This blog entry desribes three Django Authenticaon backends you can use instead of the default “django.contrib.auth.backends.ModelBackend”. Below is a brief description of each.

EmailBackend – This allows the user to use wither his or her username or his or her email address to log in. Not you have to make sure that usernames and email addresses are unique across all users in order for this to work properly.

MobilePhoneBackend -This allows the user to login with his or her mobile phone number and password instead of using the user name. This assumes you have a UserProfile model defined with the field “mobile_phone_number”.

HTTPAuthBackend – A standard HTTP Auth backend. Use this for APIs. This one just username and password, but by using this method, it is easy to access you system like a web service.

Here is an example of using the HTTPAuth backend via curl.

curl -u "myuser:mypass" -X GET 127.0.0.1:8000/

Here is the file containing the Authentication Backends

auth.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4

# A collection of handy authentication Backends. Copyright Videntity 2012.
# License: BSD

from django.contrib.auth.models import User
from django.core.validators import email_re
import binascii
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.auth.decorators import login_required
from django.template import loader
from django.contrib.auth import authenticate
from django.conf import settings
from django.core.urlresolvers import get_callable
from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import render_to_response
from django.template import RequestContext
from models import UserProfile

class BasicBackend:
    supports_anonymous_user=False
    supports_object_permissions=False
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

class EmailBackend(BasicBackend):
    supports_anonymous_user=False
    supports_object_permissions=False
    def authenticate(self, username=None, password=None):
        #If username is an email address, then try to pull it up
        if email_re.search(username):
            try:
                user = User.objects.get(email=username)
            except User.DoesNotExist:
                return None
        else:
            #We have a non-email address username we should try username
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                return None

        if user.check_password(password):
            return user

class MobilePhoneBackend(BasicBackend):
    supports_anonymous_user=False
    supports_object_permissions=False
    def authenticate(self, username=None, password=None):
        try:
            up = UserProfile.objects.get(mobile_phone_number=username)
        except UserProfile.DoesNotExist:
            return None

        if up.user.check_password(password):
            return up.user

class HTTPAuthBackend(BasicBackend):
    supports_anonymous_user=False
    supports_object_permissions=False
    def __init__(self, auth_func=authenticate, realm='API'):
        self.auth_func = auth_func
        self.realm = realm

    def is_authenticated(self, request):
        auth_string = request.META.get('HTTP_AUTHORIZATION', None)
        if not auth_string:
            return False

        try:
            (authmeth, auth) = auth_string.split(" ", 1)

            if not authmeth.lower() == 'basic':
                return False

            auth = auth.strip().decode('base64')
            (username, password) = auth.split(':', 1)
        except (ValueError, binascii.Error):
            return False

        request.user = self.auth_func(username=username, password=password) \
            or AnonymousUser()

        return not request.user in (False, None, AnonymousUser())

    def authenticate(self, request):
        auth_string = request.META.get('HTTP_AUTHORIZATION', None)

        if not auth_string:
            return AnonymousUser

        try:
            (authmeth, auth) = auth_string.split(" ", 1)

            if not authmeth.lower() == 'basic':
                return AnonymousUser

            auth = auth.strip().decode('base64')
            (username, password) = auth.split(':', 1)
        except (ValueError, binascii.Error):
            return AnonymousUser

        request.user = self.auth_func(username=username, password=password) \
            or AnonymousUser()

        return not request.user in (False, None, AnonymousUser())

    def challenge(self):
        resp = HttpResponse("Authorization Required")
        resp['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
        resp.status_code = 401
        return resp

    def __repr__(self):
        return u'<HTTPBasic: realm=%s>' % self.realm

How to Use the Backends in your Application

Define AUTHENTICATION_BACKENS in settings.py. Note that I have commended out ModelBackend. This is because the EmailBackend checks both the user/password combination and the email/password combinations.


AUTHENTICATION_BACKENDS = (#'django.contrib.auth.backends.ModelBackend',
                           'apps.accounts.auth.EmailBackend',
                           'apps.accounts.auth.MobilePhoneBackend',
                           'apps.accounts.auth.HTTPAuthBackend',
                           )

When attempting to use HTTPAuthBackend when using Apache/mod_wsgi, you’ll need to tell WSGI to pass the authorization to the Django application. You do this in a your Apache2 configuration file. On Ubuntu Linux this file might live at “/etc/apache2/sites-available/default”. In your virtualhost configuration for your Django application, you need to add the line “WSGIPassAuthorization On” right below the “WSGIScriptAlias” line.

Here is an example:

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot /var/www
	<Directory />
		Options FollowSymLinks
                AllowOverride AuthConfig

	</Directory>
	<Directory /var/www/>
		Options Indexes FollowSymLinks MultiViews
		AllowOverride None
		Order allow,deny
		allow from all
	</Directory>
        WSGIScriptAlias / /home/ubuntu/django-apps/RESTCat/config/apache/django.wsgi
        WSGIPassAuthorization On
        Alias /static/admin/ /var/www/djadminstatic/
        Alias /static/ /home/ubuntu/django-apps/RESTCat/mainstatic/

	ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
	<Directory "/usr/lib/cgi-bin">
		AllowOverride None
		Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
		Order allow,deny
		Allow from all
	</Directory>

	ErrorLog ${APACHE_LOG_DIR}/error.log

	# Possible values include: debug, info, notice, warn, error, crit,
	# alert, emerg.
	LogLevel warn

	CustomLog ${APACHE_LOG_DIR}/access.log combined

    Alias /doc/ "/usr/share/doc/"
    <Directory "/usr/share/doc/">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride None
        Order deny,allow
        Deny from all
        Allow from 127.0.0.0/255.0.0.0 ::1/128
    </Directory>

</VirtualHost>

Hope this helps someone.