Development Long read Authentication
How does authentication work in Django?

How does authentication work in Django?

Middleware order, auth backends, and request.user assignment.

4 February 2026 19 min read
Share
X in

Introduction

Middleware is a chain of hooks that run before every HTTP request reaches a view and before the response is returned. Django's session, CSRF, and authentication mechanisms largely work through middleware. Custom middleware centrally solves cross-cutting needs like rate limiting, tenant selection, or request IDs.

In this article we examine the middleware lifecycle, writing custom authentication backends, and JWT bearer token validation for API projects with real code.

Middleware Lifecycle

MiddlewareMixin (or Django 5+ MiddlewareProtocol) receives request and get_response in __call__. process_request runs before the view, process_response after. process_exception handles errors.

  • SecurityMiddleware should be first
  • SessionMiddleware before AuthenticationMiddleware
  • Custom middleware usually after auth
class RequestIDMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        request.request_id = str(uuid.uuid4())
        response = self.get_response(request)
        response['X-Request-ID'] = request.request_id
        return response

AuthenticationMiddleware and request.user

AuthenticationMiddleware sets request.user and request.auth. The default ModelBackend uses the User model and session-based login. authenticate() tries AUTHENTICATION_BACKENDS in order.

from django.contrib.auth import authenticate, login

user = authenticate(request, username='ali', password='secret')
if user is not None:
    login(request, user)

Custom Authentication Backend

You can write a custom backend with AbstractBaseUser or the existing User model. Implement get_user(user_id) for session restore and authenticate() for credential validation.

from django.contrib.auth.backends import BaseBackend

class EmailBackend(BaseBackend):
    def authenticate(self, request, email=None, password=None, **kwargs):
        User = get_user_model()
        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            return None
        if user.check_password(password):
            return user
        return None

    def get_user(self, user_id):
        User = get_user_model()
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None
The first matching backend in AUTHENTICATION_BACKENDS wins; choose order deliberately.

JWT Bearer Token Middleware

Stateless APIs use JWT instead of sessions. Parse the Authorization header, validate the token, and set request.user. PyJWT or djangorestframework-simplejwt are common choices.

import jwt
from django.contrib.auth import get_user_model

class JWTAuthenticationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        auth = request.META.get('HTTP_AUTHORIZATION', '')
        if auth.startswith('Bearer '):
            token = auth[7:]
            try:
                payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
                request.user = get_user_model().objects.get(pk=payload['sub'])
            except (jwt.InvalidTokenError, get_user_model().DoesNotExist):
                request.user = AnonymousUser()
        return self.get_response(request)

Security and Error Handling

Do not log sensitive data in middleware. Decoding tokens on every request is costly; use short-lived access tokens plus refresh tokens. Whether failed auth continues as AnonymousUser or returns 401 is a policy choice.

  • Require HTTPS; never pass tokens in query strings
  • leeway parameter for clock skew
  • Token blacklist or rotation strategy

Testing and Debugging

Write middleware unit tests with RequestFactory. Integration tests use force_login on Client or Authorization headers.

  1. Backend authenticate() unit test
  2. Middleware request/response test
  3. APIClient 401/403 scenarios
from django.test import RequestFactory

def test_jwt_middleware_sets_user():
    factory = RequestFactory()
    request = factory.get('/', HTTP_AUTHORIZATION=f'Bearer {valid_token}')
    middleware = JWTAuthenticationMiddleware(lambda r: HttpResponse('ok'))
    middleware(request)
    assert request.user.is_authenticated

Conclusion

Middleware and custom auth backends are the right way to extend Django's security model. Central middleware + backend beats parsing tokens inside monolithic views for maintainability and testing.

  • REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] for DRF
  • Separate session and JWT by URL prefix in one project
  • Test middleware order changes in staging first