Skip to main content

Command Palette

Search for a command to run...

How to login with username and/or email in Django Rest Framework.

Published
3 min read
How to login with username and/or email in Django Rest Framework.
N

I write when I experience issues that were rather tough for me to solve or I feel it's something I learned in a complicated way and I'd like to explain them in a much easier way for anyone to understand and not experience what I experienced.

This is a straight-to-the-point article, I'll assume you have previous experience with Django and the Django Rest Framework as I will not be covering that in this article. However, I'll explain some terms as needed. This article is for you if you need a clear and concise guide on how to use a username or email address to log in a user with the Django Rest Framework.

I'll be using Django Simple JWT. You can find it on PyPI Library here

To install it in your Project,

pip install djangorestframework-simplejwt

You should have already defined your User Model in the models.py of the django app you have created for authentication, for this example let's call it users. There are various ways you can do this, you can either user Django's provided User as it is, extend it to add custom fields, or override it to define your own custom User Model. Whichever you want to do it is up to you.

Next, import the User Model in the views.py of the authentication app which we named users. If you are using a custom user model -- say the name of the model is User, you would do it as;

# Django imports
from users.models import User

For this example, we would be using the default Django User model, so we would do it as;

# Django imports
from django.contrib.auth.models import User

Because we are going to customize the login behavior to use email and username, we have to create a subclass for the desired view as well as a subclass for its corresponding serializer. In the views.py, import TokenObtainPairSerializer and TokenObtainPairView from rest_framework_simplejwt.serializers which we would use to override the default authentication provided by SimpleJWT.

# Django imports
from users.models import User

# Rest Framework imports
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView

Now we come to the fun part. Here we define the subclasses and customize the default authentication method. Here's the code that does the magic;

class MyTokenObtainPairView(TokenObtainPairView):
    """
    Custom Access token View
    """
    serializer_class = MyTokenObtainPairSerializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        # get password, and username (or email)
        password = attrs.get("password")
        username = attrs.get("username")

        # set user to None
        user = None

        # check if a user exists with the email address or username provided
        if "@" in username:
            user = User.objects.filter(Q(email__iexact=username)).first()
        else:
            user = User.objects.filter(Q(username__iexact=username)).first()

        # raise an authentication failed error if a user with that username or email doesn't exist. 
        if user is None:
            raise exceptions.AuthenticationFailed(
                detail="Credentials does not exist!")

        else:
            # check if password matches
            if user.check_password(password):

                serializer = UserSerializerWithToken(user).data
                return serializer

            # raise authentication failed error if it doesn't exist. 
            else:
                raise exceptions.AuthenticationFailed(
                    detail="Wrong password")

For exception handling, import exceptions from rest framework.

from rest_framework import exceptions

The resulting views.py should look like this;

# Django imports
from users.models import User

# Rest Framework imports
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework import exceptions

class MyTokenObtainPairView(TokenObtainPairView):
    """
    Custom Access token View
    """
    serializer_class = MyTokenObtainPairSerializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        # get password, and username (or email)
        password = attrs.get("password")
        username = attrs.get("username")

        # set user to None
        user = None

        # check if a user exists with the email address or username provided
        if "@" in username:
            user = User.objects.filter(Q(email__iexact=username)).first()
        else:
            user = User.objects.filter(Q(username__iexact=username)).first()

        # raise an authentication failed error if a user with that username or email doesn't exist. 
        if user is None:
            raise exceptions.AuthenticationFailed(
                detail="Credentials does not exist!")

        else:
            # check if password matches
            if user.check_password(password):

                serializer = UserSerializerWithToken(user).data
                return serializer

            # raise authentication failed error if it doesn't exist. 
            else:
                raise exceptions.AuthenticationFailed(
                    detail="Wrong password")

Lastly, remember to point to the authentication class in your like so;

# Importing the views.py file
from users import views

# Django Imports
from django.urls import path

urlpatterns = [
    ...
    path('login/', views.MyTokenObtainPairView.as_view(), name='login'),
    ...
]

Fire up your server and go! If you have any questions, ask them below.

#2articles1week #python #django #drf #djangorestframework #programming #webdevelopment #softwaredevelopment