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
Login
User
All users
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
Learn how to create a boilerplate project in order to start your next project faster
#backend