Step-by-Step JWT Authentication Implementation in Python - Part 1

sanjana kansalsanjana kansal
4 min read

In this article, we will explore the concept of JWT authentication and learn how to implement it using PyJWT.

Prior knowledge of Django and API building in Django is necessary to understand this article.

JWT (JSON Web Token) is a token-based authentication mechanism that allows clients to send an access token to the server for authentication.

Users who sign up for an application provide their username and password. Upon successful login, the server issues both an access token and a refresh token to the user. The access token has a limited lifespan, varying depending on the specific use case. Once the access token expires, the user can use the refresh token to request a new access token by hitting the token API.

We will create Signup, Login, Token APIs, and AuthMiddleware to add for protected routes.

To implement JWT authentication using PyJWT, follow these steps:

Let's start by creating the SignUp API.

Set up the Project and Virtual Environment.

Create a requirements.txt file to manage dependencies.

Django==3.2.20
django-tastypie==0.14.5
PyJWT==2.8.0
marshmallow==3.7.1

To install the dependencies listed in the requirements.txt, run the following command:

pip install -r requirements.txt

Start a Django project.

django-admin startproject custom_auth .

Migrate the model changes which came from installed apps and run server.

python manage.py migrate
python manage.py runserver

This should start the server at 127.0.0.1:8000.

Start an application by running the below command.

python manage.py startapp authentication

Include the authentication app in INSTALLED_APPS in settings.py.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'authentication'
]

Create a api.py file in the authentication app. Inside, define a UserResource for handling API related to users.

Add a route for the SignUp, Login, and Token API, which we will create.

from django.conf.urls import url
from tastypie.resources import Resource
from tastypie.utils import trailing_slash


class UserResource(Resource):
    class Meta:
        resource_name = 'user'

        def prepend_urls(self):
        return [
            url(
                r"^(?P<resource_name>%s)/signup%s$"
                % (self._meta.resource_name, trailing_slash()),
                self.wrap_view('signup'),
                name="api_signup",
            ),
            url(
                r"^(?P<resource_name>%s)/login%s$"
                % (self._meta.resource_name, trailing_slash()),
                self.wrap_view('login'),
                name="api_login",
            ),
            url(
                r"^(?P<resource_name>%s)/token%s$"
                % (self._meta.resource_name, trailing_slash()),
                self.wrap_view('token'),
                name="api_token",
            ),
        ]

Add UserResource route in urls.py of custom_auth project.

from django.conf.urls import url
from django.contrib import admin
from django.urls import path, include
from tastypie.api import NamespacedApi
from authentication.api import UserResource


v1_api = NamespacedApi(api_name='v1')
v1_api.register(UserResource())

urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^api/', include(v1_api.urls)),
]

Update models.py in the authentication app.

from django.db import models


# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=255, unique=True)
    email = models.CharField(max_length=512, unique=True)
    password = models.CharField(max_length=512)
    first_name = models.CharField(max_length=255, blank=True, null=True)
    last_name = models.CharField(max_length=255, blank=True, null=True)

Create exceptions.py in the app.

class ApiException(Exception):
    error_code = "-"
    message = "-"


class UserAlreadyExists(ApiException):
    error_code = "UserAlreadyExists"
    message = "User already exists."


class UserDoesNotExists(ApiException):
    error_code = "UserDoesNotExists"
    message = "User does not exists."


class InvalidData(ApiException):
    error_code = "InvalidData"
    message = "Invalid Data."

Create dal.py in the app.

from authentication import models
from django.db import IntegrityError
from authentication import exceptions


def create_user(*args, **kwargs):
    data = kwargs["data"]
    try:
        return models.User.objects.create(**data)
    except IntegrityError:
        raise exceptions.UserAlreadyExists

Add validations.py in the app.

from marshmallow import Schema, fields


class SignupSchema(Schema):
    username = fields.Str(max_length=255)
    email = fields.Str(max_length=512, required=True)
    password = fields.Str(max_length=512, required=True)
    first_name = fields.Str(max_length=255)
    last_name = fields.Str(max_length=255)

Write the signature of the SignUp API.

    def signup(self, request, *args, **kwargs):
        request_data = json.loads(request.body)
        try:
            validations.SignupSchema().load(request_data)
        except ValidationError as e:
            return self.error_response(request, {'error_code': 'ValidationError', 'message': e.messages})
        kwargs["data"] = request_data
        try:
            user = dal.create_user(*args, **kwargs)
        except exceptions.ApiException as e:
            return self.error_response(request, {'error_code': e.error_code, 'message': e.message, "success": False})
        return self.create_response(
            request,
            {
                "success": True,
                "user": {
                    "username": user.username,
                    "email": user.email,
                    "first_name": user.first_name,
                    "last_name": user.last_name
                }
            }
        )

Make an hash.py to hash the password before storing it in the database.

import hashlib


def hash_password(password):
    password_bytes = password.encode('utf-8')
    hash_object = hashlib.sha256(password_bytes)
    password_hash = hash_object.hexdigest()
    return password_hash

Override the save method of the User model.

from django.db import models
from authentication import hash


# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=255, unique=True)
    email = models.CharField(max_length=512, unique=True)
    password = models.CharField(max_length=512)
    first_name = models.CharField(max_length=255, blank=True, null=True)
    last_name = models.CharField(max_length=255, blank=True, null=True)

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        self.password = hash.hash_password(self.password)
        super(User, self).save()

Make migrations and run the migrations.

python manage.py makemigrations
python manage.py migrate

Congratulations! The signup API is complete. We can now test it out.


1
Subscribe to my newsletter

Read articles from sanjana kansal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

sanjana kansal
sanjana kansal