Creating a custom authentication backend for Django is actually quite easy.

What might they be used for?

  • E-mail based usernames
  • Third party authentication - ie Facebook, Other Web Service, POP3/IMAP, LDAP

For this tutorial I will create a IMAP authentication backend which allows checking a username and password against an E-mail server - perfect if you are writing your own webmail client or address book.

What is an Authentication Backend?

Authentication backends allow the ability to change what method checks your users credentials.

A great example of this is to create an IMAP authentication backend. Rather than creating a username & password in 2 seperate databases - your Django application and your mail server, you would want to have just the one.

For webservices, ie Facebook authentication, you don’t have access to user data. Facebook connect provides you details of the currently authenticated user. But to maintain the login_required decorator or to user request.user you still need to have them logged in using Django. That’s where the Authentication Backend comes in.

Creating a Simple Authentication Backend

This is only a simple backend and isn’t really useful beyond an example. More experienced users may want to skip this step

For this example we are going to check to see if a user matching the username exists and that the password is their username in reverse.

So, assuming you have a user called admin, the following would be correct;

Username: admin

Password: nimda

# import the User object
from django.contrib.auth.models import User

# Name my backend 'MyCustomBackend'
class MyCustomBackend:

    # Create an authentication method
    # This is called by the standard Django login procedure
    def authenticate(self, username=None, password=None):

        try:
            # Try to find a user matching your username
            user = User.objects.get(username=username)

            #  Check the password is the reverse of the username
            if password == username[::-1]:
                # Yes? return the Django user object
                return user
            else:
                # No? return None - triggers default login failed
                return None
        except User.DoesNotExist:
            # No user was found, return None - triggers default login failed
            return None

    # Required for your backend to work properly - unchanged in most scenarios
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Making Django use your new Authentication Backend.

In your settings.py add

AUTHENTICATION_BACKENDS = ( 'path.to.your.MyCustomBackend', )

You might have ‘project.backend.MyCustomBackend’ - this could be backend.py in your project file, with a class name of MyCustomBackend

Yes, security wise this is pretty pointless, but its demonstrating how a simple authentication backend works.

Creating the IMAP Authentication Backend

To communicate with the IMAP server I will be using the imaplib as provided by the default python packages. Here is a basic example of login using this library

from imaplib import IMAP4

try:
    c = IMAP4('my_mail_server')
    c.login('my_username', 'my_password')
    c.logout()
except IMAP4.error, e:
    print 'An error occurred: %s' % e

All we need to do now is combine this with our Django backend code.

# import the User object
from django.contrib.auth.models import User

# import the IMAP library
from imaplib import IMAP4

# import time - this is used to create Django's internal username
import time

# Name my backend 'MyCustomBackend'
class MyCustomBackend:

    # Create an authentication method
    # This is called by the standard Django login procedure
    def authenticate(self, username=None, password=None):
        try:
            # Check if this user is valid on the mail server
            c = IMAP4('my_mail_server')
            c.login(username, password)
            c.logout()
        except:
            return None

        try:
            # Check if the user exists in Django's local database
            user = User.objects.get(email=username)
        except User.DoesNotExist:
            # Create a user in Django's local database
            user = User.objects.create_user(time.time(), username, 'passworddoesntmatter')

        return user

    # Required for your backend to work properly - unchanged in most scenarios
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

In the above example I have assumed that your mail server is configured to use username@domain.com usernames. If not, you could modify as follows;

Replace

    try:
        # Check if the user exists in Django's local database
        user = User.objects.get(email=username)
    except User.DoesNotExist:
        # Create a user in Django's local database
        user = User.objects.create_user(time.time(), username, 'passworddoesntmatter')

With

	# Check if the user exists, if not, create it.
    user, created = User.objects.get_or_create(username=username)

You can also remove the import time as this was only used when creating a user when you needed something unique - the real username takes care of that.

Making Django use your new Authentication Backend.

In your settings.py add

AUTHENTICATION_BACKENDS = ( 'path.to.your.MyCustomBackend', )

I think I have documented the code above well enough to understand without needing to go into too much detail.