DEV Community

Cover image for Creating a Custom Authentication Backend In Django
Kelvin Amoaba
Kelvin Amoaba

Posted on

Creating a Custom Authentication Backend In Django

You may have noticed that the built in authentication system in Django has quite some few limitations. For example, it only allows your website's users to only log in using their username and password. But in modern day websites, you might want to enable your users to be able to log in to your site using either their username or email address. If your website is for a company, you can even implement an authentication backend to make users log in using lets say a secret key.

What is an authentication backend in django?
An authentication backend is a python class which provides two methods for authenticating a user and also retrieving a user.

To implement a custom authentication backend, you need to define a python class with two(2) methods. One for retrieving a user and the other for authenticating a user. The two methods are, get_user() and authenticate() respectively.

The authenticate() method takes in a request object and the user credentials as parameters. It then returns a user object that matches the given credentials, if they are valid. It returns None if the credentials are not valid.
get_user() method takes a user ID as a parameter and has to return a user.

In this tutorial, we are going to create a new authentication backend to allow users login using their email addresses also.

NOTE: THIS TUTORIAL ASSUMES YOU HAVE ALREADY CONFIGURED A VIRTUAL ENVIRONMENT AND ALSO CREATED A NEW DJANGO PROJECT.

Let's create a new django application called account by running the command in your project root directory.:

python manage.py startapp account
Enter fullscreen mode Exit fullscreen mode

Add account to your installed applications.

The account app should have the following directory structure.

The green dots there just mean they are untracked by git
Next, create a new file called authentication.py in the account app.
In authentication.py, we add the following code:

from django.contrib.auth.models import User
class EmailAuthBackend:
    """
    Custom authentication backend.

    Allows users to log in using their email address.
    """

    def authenticate(self, request, username=None, password=None):
        """
        Overrides the authenticate method to allow users to log in using their email address.
        """
        try:
            user = User.objects.get(email=username)
            if user.check_password(password):
                return user
            return None
        except User.DoesNotExist:
            return None

    def get_user(self, user_id):
        """
        Overrides the get_user method to allow users to log in using their email address.
        """
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None
Enter fullscreen mode Exit fullscreen mode

Remember, the authenticate() method will be used to authenticate your users, and will be called when you call the authenticate() method in your view.
In the authenticate() method above, we are having the parameters request, username and password. The entered email address will be found in the username parameter and the password in the password parameter.

We are using a try, except block because, fetching a user that does not exist will raise a DoesNotExist error.
We retrieve a user with the incoming email address, note that the email address must be set to unique=True otherwise a MultipleObjectsReturned error will be raised if more than one user has the same email address. This can be done by creating a custom user model. If you're not familier with a custom user model, let me know in the comments.

Next, we're using the method, user.check_password(). This will take the password argument, hash it and then compare it with the hashed password in the database. If there is a match, it will return True, else False.
In our case, if the result is True, we return the user, else we return None.
Also, if there is no user in the database with that email address, in that case a user.DoesNotExist error will be raised. If there's no user, we return None.

The get_user() method takes a user_id as an argument. It returns the user if it exists or else it returns None.

Thats it, we are done with our custom authentication backend. Phew.
But now, users will still not be able to log in using their email address. This is because, we have not told django to add our authentication backend to the list of authentication backends available. To do that we need to edit our settings.py file.
Add the following to it.

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend', # This is the default that allows us to log in via username
    'account.authentication.EmailAuthBackend'
]
Enter fullscreen mode Exit fullscreen mode

Now, django is aware of our authentication backend.

But how does django apply our authentication backend?
When we call the authenticate() method from django.contrib.auth in our view, django authenticates the user agains each of the authentication backend specified in the AUTHENTICATION_BACKENDSsetting. In other words, it calls the authenticate method of each of the authentication backends specified.
Django will stop at the first backend that authenticates successfully.

Go ahead and run your development server and try out your new authentication method.
You can extend this to authenticate against whatever you wish.

Thanks for reading. If you have any questions, please let me know in the comments section. And also, dont forget to follow for more tutorials.
If you would like to request for a tutorial, let me know. Thanks and happy coding.

Latest comments (1)

Collapse
 
geethakrishnach profile image
geethaKrishna

hi @amoabakelvin, I have a similar use case in wagtail, i want to integrate an external api to authenticate the wagtail users, does this work in wagtail as well? Because in wagtail users will have certail roles and I am wondering how can i tell wagtail the logged in user is admin or editor from my api response. Any suggestion is appreciated. Thanks...