Custom authentication back-ends

An authentication process is important because it determines which users are allowed access to an application. The default authentication process used by Django consists of comparing a username and password -- provided on a web form -- against User records in a database. If the username and password match against a User record, the authentication process is deemed successful, but if the values don't match, then the authentication process is deemed a failure.

Django itself includes a series of built-in authentication back-end classes[2] to support variations of this authentication process. In addition, in the final section of this chapter I'll introduce you to the all-auth Django package which supports a series authentication back-ends (e.g. authentication against social media accounts).

But to illustrate the concept of a custom authentication back-end from the ground up, I'll create a simple authentication back-end that relies on emails for authentication and can use any user type (i.e. custom user models or the default User model).

By default, Django projects use the django.contrib.auth.backends.ModelBackend authentication back-end class, designed to compare username and password sets -- provided by an end user -- against a project's users in a database. Now ask yourself, what do you think is easier to remember as a log in credential, a username or an email ? If you're like most people in this day in age, you're more likely to have answered email.

Listing 10-16 illustrates a custom authentication back-end that is able to authenticate users by means of an email credential and not the default username.

Listing 10-16. Custom authentication back-end to support authentication with email

# (registration app)
from django.contrib.auth import get_user_model

class EmailBackend(object):
    def authenticate(self, request, username=None, password=None, **kwargs):
        User = get_user_model()
            user = User.objects.get(email=username)
        except User.DoesNotExist:
            return None
            if getattr(user, 'is_active', False) and  user.check_password(password):
                return user
        return None
    def get_user(self, user_id):
        User = get_user_model()        
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend',       

Listing 10-16 declares the EmailBackend class for the custom authentication back-end. Like all custom authentication back-end classes, you must declares at minimum the authenticate() and get_user() methods, where the first method is used to define the authentication logic and the second to return the user of the request.

The authenticate() method gains access to both the username and password fields provided by an end user, as part of the base authentication workflow. Next, you can see the authenticate() method gains access to a project's user model relying on the get_user_model() method helper, which ensures that even if a project uses a custom user model, the authentication workflow is done on the correct user class, as described in the previous section.

Once a reference is obtained to a project's user class, notice the authenticate() method performs a query for a user on the email field with the provided username, which effectively allows the username value provided by a user to be treated as an email field for authentication purposes. If a user matches the email provided as the username value, then the try-except-else block in listing 10-16 performs a call to check_password() to validate the password against the matching user. If the password matches, authenticate() returns the authenticated user, if the password doesn't match authententicate() returns None.

The final section in listing 10-16 is the AUTHENTICATION_BACKENDS variable in, which is assigned a list of authentication back-end classes. In this case, the default django.contrib.auth.backends.ModelBackend class is kept to ensure the default authentication workflow of username/password is attempted first. Next, the custom EmailBackend authentication back-end class from listing 10-16 is added, to ensure the authentication workflow is done treating the input data as an email/password set.

As you can see from this example in listing 10-16, by adding this simple custom authentication back-end class, you can allow users to introduce either their username or email to authenticate themselves in a Django application.

Tip If you use the custom authentication back-end class in listing 10-16, change the username label in the log-in form to 'Username/Email' to notify users they can use both.