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
# models.py (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() try: user = User.objects.get(email=username) except User.DoesNotExist: return None else: 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() try: return User.objects.get(pk=user_id) except User.DoesNotExist: return None # setting.py AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'coffeehouse.registration.models.EmailBackend']
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
settings.py
, 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.