Creating a Custom Authentication Backend
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 [email protected] 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.