How to setup user authentication with Django Rest Framework and JWT

User authentication is one of the things that we repeat every time we create a new project and once you have finished your boilerplate is the next thing I use to set up if I need that functionality so with that said, we’ll see how you can create a custom user model including authentication endpoints with JSON Web Tokens.

Installing JWT

First up, we are going to install JWT for Django to be able to generate our authentication token that we need to send POST HTTP requests that need a user logged in.

pip install djangorestframework-simplejwt

Once we do that, we can use this library a bit later.

Settings configuration

Now we need to open our settings.py file to add some configuration.

# ...other imports
import timedelta

# ...settings code

AUTH_USER_MODEL = 'users.UserProfile'

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        )
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': True,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',

    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

# ...more code

Create users app

Once we’ve finished with our JWT setup, we need to go ahead and create our user app for our project. We do that by opening our terminal and running the following command:

django-admin start app users

That will create a new directory in our project with the necessary files and ready to be registered.

Register app

The next step is to add our newly created app. We open the settings.py files and add the users application inside the INSTALLED_APPS array —Register app code

Create user model

Now we are ready to create our custom user model. The first thing to do is to open the models.py file and import the base models from django.contrib.auth.models.

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager

Then we create our first model which will be the user manager capable of creating our superusers and the regular users and will have two methods the create_user and create_superuser both with “self, email, username, password’’ parameters.

class UserProfileManager(BaseUserManager):
    """Manager for user profiles"""

    def create_user(self, email, username, password=None):
        if not email:
            raise ValueError('Users must have an email')

        email = self.normalize_email(email)
        user = self.model(email=email, username=username)

        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_superuser(self, email, username, password):
        user = self.create_user(email, username, password)

        user.is_superuser = True
        user.is_staff = True
        user.save(using=self._db)

The create_user method will create a new user in our database but first will validate if the email field is empty and will raise an error stating that the email is already registered, then we’ll set the email, user, and password from the parameters, save them in our database and return the user at the end.

The create_superuser is more straightforward due to we are reusing all of the logic from our create_user and adding some extra properties like the is_superuser and is_staff.

Now that we have our user manager model we can create our userProfile model which will include all of the necessary fields for our user.

We need to create a class with AbstractBaseUser and PermissionsMixins models that we imported earlier as parameters. We proceed to create the following fields:

class UserProfile(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(max_length=255, unique=True)
    username = models.CharField(max_length=255, unique=True)
    avatar = models.URLField(blank=True, null=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = UserProfileManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    def __str__(self):
        return self.email

As you can see we created an object variable that is referencing our userProfileManager model in order to use this for our ORM queries in our views later. Also, we declare a constant named USERNAME_FIELD that is equal to "username" in order to be able to log in with our username instead of the email.

Create serializers for user

Once we have our user model, we can create our serializer which is the way Django Rest Framework “translate” the python data into JSON data and vice versa by creating a file named serializer.py inside of our user folder. Inside the file we are going to start by importing serializers from rest_framework and also our UserProfile model from .models

from rest_framework import serializers
from .models import UserProfile

Then we need to create a class with the name of our model followed by the word Serializer for convention and passing serializers.ModelSerializer as a parameter from rest_framework. Inside the class we need to create another class named Meta which is going to include the model we are serializing and the fields we want to show in our JSON response and additionally include some extra_kwargs where we are telling that the password field is “write-only”. Finally, we create a method that will create a new row in our database with the new user when we register a new one.

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ('id', 'email', 'username', 'password', 'avatar')
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = UserProfile.objects.create_user(**validated_data)
        return user

Create views

Now we are ready to create our views which are basically classes that run HTTP methods with the corresponding ORM queries and business logic inside when a URL has been called. We are going to start up by importing all the things we need

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from .models import UserProfile
from .serializers import UserProfileSerializer

Then we can create our first view which is going to be called RegisterUserView and we are going to pass APIView that was imported earlier. This - as the name states - is going to be the view we want to use when the /users/register URL is called.

Inside our class, we are going to add our parser_classes variable which is going to be equal to the parsers we imported as well. Then we create a post method with the self and request parameters. Inside our method, we first are going to validate if the email has been already registered by searching for it in our database and raise an error if so, then we are going to serialize our data from our request, save the serializer and return a response with the serialized data and the corresponding HTTP status

class RegisterUserView(APIView):
    parser_classes = [JSONParser, MultiPartParser, FormParser]
    def post(self, request):

        # if email is already in use
        if UserProfile.objects.filter(email=request.data['email']).exists():
            return Response({'error': 'Email already registered'}, status=status.HTTP_400_BAD_REQUEST)
        else:
            serializer = UserProfileSerializer(data=request.data)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The second view we need is the UserView with the same APIView parameter which is going to return the authenticated user that makes the request excluding the password. Inside the class, we are going to add the permission_classes variable that is going to be equal to a tuple with the "IsAuthenticated" permission from Django Rest Framework and the parser_classes.

The GET method is only going to serialize the user from the request also we are going to create a PUT method in order to be able to change our user avatar image. The code should look like this:

class UserView(APIView):
    permission_classes = (IsAuthenticated,)
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    def get(self, request):
        serializer = UserProfileSerializer(request.user, many=False)
        return Response(serializer.data, status=status.HTTP_200_OK)

    # update user profile image
    def put(self, request):
        user = UserProfile.objects.get(email=request.user.email)
        user.avatar = request.data['avatar']
        user.save()
        return Response({'message': 'Image updated'}, status=status.HTTP_200_OK)

Finally, our last view is going to be the AllUsersView with the APIView parameter as well as the "IsAuthenticated" permission class which is going to return all of our users and return the serialized response.

class AllUsersView(APIView):
    permission_classes = (IsAuthenticated,)

    def get(self, request):
        users = UserProfile.objects.all()
        serializer = UserProfileSerializer(users, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

— You may be wondering why we didn’t create a login view and that’s because we are going to take care of that with JWT.

Create routes

Now we are going to create a url.py file in order to create the endpoints for our API. First off, we are going to import the path from django.urls, all of our views from .views and the views for creating our access token, and the refresh token from JWT.

from django.urls import path
from .views import RegisterUserView, LogoutUserView, UserView, AllUsersView
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

Finally, we are going to create our urlpatterns variable with the list of paths including the URL and the class view we are going to use for them.

It should look like this.

urlpatterns = [
    path('', AllUsersView.as_view()),
    path('user/', UserView.as_view()),
    path('register/', RegisterUserView.as_view()),
    path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

Trying with thunder client

We can give it a try to the endpoints using Postman or the Thunder client extension in VSCode

Register

register view

Login

login view

User

user view

All users

all users view

Now you can use this custom user and the authentication endpoints in your next project that needs authentication and save a lot of time. I hope this was helpful and don’t miss the next post about Django.

Source

Look at the Django Rest Framework documentation to learn more.

Look at the Simple JWT documentation to learn more.

Related posts

How to create a Django Rest Framework boilerplate project

How to create a Django Rest Framework boilerplate project

Learn how to create a boilerplate project in order to start your next project faster

#backend