Why SimpleJWT Over Session Auth for APIs

If you're building a React or Next.js frontend that talks to a Django backend, sessions don't work well across origins. JWT (JSON Web Token) authentication is the standard for decoupled frontends.

Installation

pip install djangorestframework-simplejwt
# settings.py
from datetime import timedelta

INSTALLED_APPS += ["rest_framework", "rest_framework_simplejwt"]

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

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=15),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": True,
    "AUTH_HEADER_TYPES": ("Bearer",),
}

URLs

from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenBlacklistView,
)

urlpatterns = [
    path("api/auth/token/",         TokenObtainPairView.as_view()),
    path("api/auth/token/refresh/", TokenRefreshView.as_view()),
    path("api/auth/logout/",        TokenBlacklistView.as_view()),
]

Custom Claims (Add User Data to Token)

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class CustomTokenSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        token["name"]  = user.get_full_name()
        token["role"]  = user.profile.role
        token["email"] = user.email
        return token

Storing Tokens Securely on the Frontend

Never store JWTs in localStorage if you can avoid it - XSS can steal them. Use httpOnly cookies instead:

# Custom login view that sets cookie
class CookieTokenView(TokenObtainPairView):
    def post(self, request, *args, **kwargs):
        response = super().post(request, *args, **kwargs)
        if response.status_code == 200:
            response.set_cookie(
                "access", response.data["access"],
                httponly=True, secure=True, samesite="Lax",
                max_age=15 * 60
            )
        return response

This is the exact setup I use for all my client portals.