CodeX
Published in

CodeX

Django Rest Framework Custom JWT Authentication Backend

JWT Implementation for DRF with Phone Number

Photo by Kelly Sikkema on Unsplash

Roadmap

Custom User Model

# apps/management/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
MALE = 'Male'
FEMALE = 'Female'
GENDER_IN_CHOICES = [
(MALE, 'Male'),
(FEMALE, 'Female'),
]
phone_number = models.CharField(max_length=20, blank=True, null=True, unique=True)
gender = models.CharField(max_length=6, choices=GENDER_IN_CHOICES, null=True, blank=True)
country = models.CharField(max_length=120, null=True, blank=True)
city = models.CharField(max_length=120, null=True, blank=True)
state = models.CharField(max_length=120, null=True, blank=True)
is_approved_to_be_in_touch = models.BooleanField(default=False)
# settings.py

# ...

# custom user model
AUTH_USER_MODEL = "management.User"

# ...
# any file that needs to use User model

from django.contrib.auth import get_user_model

User = get_user_model()

Custom Authentication Backend

# apps/management/authentication.py

from datetime import datetime, timedelta

import jwt
from django.conf import settings
from django.contrib.auth import get_user_model
from rest_framework import authentication
from rest_framework.exceptions import AuthenticationFailed, ParseError

User = get_user_model()


class JWTAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
# Extract the JWT from the Authorization header
jwt_token = request.META.get('HTTP_AUTHORIZATION')
if jwt_token is None:
return None

jwt_token = JWTAuthentication.get_the_token_from_header(jwt_token) # clean the token

# Decode the JWT and verify its signature
try:
payload = jwt.decode(jwt_token, settings.SECRET_KEY, algorithms=['HS256'])
except jwt.exceptions.InvalidSignatureError:
raise AuthenticationFailed('Invalid signature')
except:
raise ParseError()

# Get the user from the database
username_or_phone_number = payload.get('user_identifier')
if username_or_phone_number is None:
raise AuthenticationFailed('User identifier not found in JWT')

user = User.objects.filter(username=username_or_phone_number).first()
if user is None:
user = User.objects.filter(phone_number=username_or_phone_number).first()
if user is None:
raise AuthenticationFailed('User not found')

# Return the user and token payload
return user, payload

def authenticate_header(self, request):
return 'Bearer'

@classmethod
def create_jwt(cls, user):
# Create the JWT payload
payload = {
'user_identifier': user.username,
'exp': int((datetime.now() + timedelta(hours=settings.JWT_CONF['TOKEN_LIFETIME_HOURS'])).timestamp()),
# set the expiration time for 5 hour from now
'iat': datetime.now().timestamp(),
'username': user.username,
'phone_number': user.phone_number
}

# Encode the JWT with your secret key
jwt_token = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')

return jwt_token

@classmethod
def get_the_token_from_header(cls, token):
token = token.replace('Bearer', '').replace(' ', '') # clean the token
return token
# settings.py

# ...

# REST FRAMEWORK
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'apps.management.authentication.JWTAuthentication',
]
}

# ...

Obtain Token API Serializer

# apps/management/api/serializers.py

from rest_framework import serializers

class ObtainTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()

Obtain Token API View

from django.contrib.auth import get_user_model
from rest_framework import views, permissions, status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from apps.management.api.serializers import ObtainTokenSerializer
from apps.management.api.serializers import UserSerializer
from apps.management.authentication import JWTAuthentication

User = get_user_model()

class ObtainTokenView(views.APIView):
permission_classes = [permissions.AllowAny]
serializer_class = ObtainTokenSerializer

def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)

username_or_phone_number = serializer.validated_data.get('username')
password = serializer.validated_data.get('password')

user = User.objects.filter(username=username_or_phone_number).first()
if user is None:
user = User.objects.filter(phone_number=username_or_phone_number).first()

if user is None or not user.check_password(password):
return Response({'message': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST)

# Generate the JWT token
jwt_token = JWTAuthentication.create_jwt(user)

return Response({'token': jwt_token})

Test The View

Image by Author | Login via phone number
Image by Author | Login via username
AUTHORIZATION: Bearer <TOKEN>

Finally

--

--

Everything connected with Tech & Code. Follow to join our 1M+ monthly readers

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Baysan

Lifelong learner & Freelancer. I use technology that helps me. I’m currently working as a Business Intelligence & Backend Developer. mebaysan.com