JSON Web Token based authentication for Django REST framework
—
Validation and processing classes for JWT authentication workflows, handling user credentials, token verification, and refresh operations. These serializers integrate with Django REST Framework's validation system to process JWT authentication requests.
Validates username/password credentials and generates JWT tokens for successful authentication.
class JSONWebTokenSerializer(Serializer):
# Dynamic fields based on user model
# username_field: CharField (dynamically added based on User.USERNAME_FIELD)
# password: PasswordField (write-only)
def __init__(self, *args, **kwargs):
"""
Dynamically adds username field based on User model configuration.
Adds:
- Username field (name from User.USERNAME_FIELD)
- Password field (write-only with password input type)
"""
@property
def username_field(self):
"""
Returns the username field name from the User model.
Returns:
str: Field name used for username (e.g., 'username', 'email')
"""
def validate(self, attrs):
"""
Validates credentials and returns token with user data.
Args:
attrs (dict): Input data containing username and password
Returns:
dict: Contains 'token' (JWT string) and 'user' (User instance)
Raises:
ValidationError: For invalid credentials, inactive users, or missing fields
Validation Process:
1. Extracts username and password from input
2. Authenticates user via Django's authenticate()
3. Checks if user account is active
4. Generates JWT payload and token
5. Returns token and user data
"""Abstract base class providing common token validation functionality for verification and refresh operations.
class VerificationBaseSerializer(Serializer):
token = CharField()
def validate(self, attrs):
"""
Abstract validation method to be implemented by subclasses.
Args:
attrs (dict): Input data containing token
Raises:
NotImplementedError: Must be overridden by subclasses
"""
def _check_payload(self, token):
"""
Validates JWT token and returns decoded payload.
Args:
token (str): JWT token string
Returns:
dict: Decoded JWT payload
Raises:
ValidationError: For expired, malformed, or invalid tokens
Validation includes:
- Token signature verification
- Expiration time checking
- Payload structure validation
"""
def _check_user(self, payload):
"""
Validates user from JWT payload and returns User instance.
Args:
payload (dict): Decoded JWT payload
Returns:
User: Active user instance
Raises:
ValidationError: For missing users, inactive accounts, or invalid payloads
Validation includes:
- Username extraction from payload
- User existence verification
- User account status checking
"""Validates JWT tokens and confirms their authenticity without generating new tokens.
class VerifyJSONWebTokenSerializer(VerificationBaseSerializer):
def validate(self, attrs):
"""
Verifies token validity and returns token with user data.
Args:
attrs (dict): Input data containing token to verify
Returns:
dict: Contains 'token' (original JWT) and 'user' (User instance)
Raises:
ValidationError: For invalid, expired, or malformed tokens
Process:
1. Validates token signature and expiration
2. Extracts and validates user from payload
3. Returns original token and user data
"""Validates existing tokens and generates new tokens with extended expiration times.
class RefreshJSONWebTokenSerializer(VerificationBaseSerializer):
def validate(self, attrs):
"""
Validates token and generates refreshed token with new expiration.
Args:
attrs (dict): Input data containing token to refresh
Returns:
dict: Contains 'token' (new JWT) and 'user' (User instance)
Raises:
ValidationError: For invalid tokens, expired refresh windows, or missing orig_iat
Process:
1. Validates existing token signature and structure
2. Extracts and validates user from payload
3. Checks original issued-at time (orig_iat) for refresh eligibility
4. Verifies refresh hasn't exceeded maximum allowed time
5. Generates new token with extended expiration
6. Preserves original issued-at time in new token
Requirements:
- Token must contain 'orig_iat' field
- Current time must be within refresh expiration window
- JWT_ALLOW_REFRESH must be enabled in settings
"""from rest_framework_jwt.serializers import JSONWebTokenSerializer
from rest_framework import serializers
from django.contrib.auth import authenticate
class CustomJWTSerializer(JSONWebTokenSerializer):
# Add extra fields
remember_me = serializers.BooleanField(default=False)
def validate(self, attrs):
# Get standard validation result
validated_data = super().validate(attrs)
# Customize token expiration based on remember_me
if attrs.get('remember_me'):
user = validated_data['user']
# Generate custom payload with longer expiration
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
from datetime import timedelta
payload = jwt_payload_handler(user)
payload['exp'] = payload['exp'] + timedelta(days=30) # Extend expiration
validated_data['token'] = jwt_encode_handler(payload)
return validated_datafrom rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from rest_framework import serializers
class CustomVerifySerializer(VerifyJSONWebTokenSerializer):
def validate(self, attrs):
# Standard validation
validated_data = super().validate(attrs)
# Add custom business logic
user = validated_data['user']
# Check additional user requirements
if not user.profile.api_access_enabled:
raise serializers.ValidationError("API access is disabled for this user")
# Log token verification
self.log_token_verification(user, attrs['token'])
return validated_data
def log_token_verification(self, user, token):
# Custom logging logic
import logging
logger = logging.getLogger('jwt_auth')
logger.info(f"Token verified for user {user.username}")from rest_framework_jwt.serializers import JSONWebTokenSerializer, RefreshJSONWebTokenSerializer
# Manual authentication
auth_serializer = JSONWebTokenSerializer(data={
'username': 'testuser',
'password': 'testpass123'
})
if auth_serializer.is_valid():
token = auth_serializer.validated_data['token']
user = auth_serializer.validated_data['user']
print(f"Authentication successful for {user.username}")
else:
print(f"Authentication failed: {auth_serializer.errors}")
# Manual token refresh
refresh_serializer = RefreshJSONWebTokenSerializer(data={
'token': token
})
if refresh_serializer.is_valid():
new_token = refresh_serializer.validated_data['token']
print(f"Token refreshed successfully")
else:
print(f"Token refresh failed: {refresh_serializer.errors}")from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.serializers import JSONWebTokenSerializer
class CustomAuthView(APIView):
def post(self, request):
serializer = JSONWebTokenSerializer(data=request.data)
if serializer.is_valid():
# Custom response formatting
response_data = {
'success': True,
'token': serializer.validated_data['token'],
'user': {
'id': serializer.validated_data['user'].id,
'username': serializer.validated_data['user'].username,
},
'message': 'Authentication successful'
}
return Response(response_data)
else:
return Response({
'success': False,
'errors': serializer.errors
}, status=400)The JSONWebTokenSerializer dynamically adds the username field based on your User model configuration:
# If User.USERNAME_FIELD = 'email'
# Serializer will have 'email' field instead of 'username'
# If User.USERNAME_FIELD = 'username'
# Serializer will have 'username' field (default)
# Custom user model example
class CustomUser(AbstractUser):
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email' # Use email for authentication
# JSONWebTokenSerializer will automatically use 'email' fieldThe password field is automatically configured with:
Serializers raise ValidationError for various conditions:
from rest_framework import serializers
# Authentication errors
{
"non_field_errors": ["Unable to log in with provided credentials."]
}
# Field validation errors
{
"username": ["This field is required."],
"password": ["This field is required."]
}
# User account errors
{
"non_field_errors": ["User account is disabled."]
}
# Token validation errors
{
"token": ["This field is required."]
}
# JWT-specific errors
{
"non_field_errors": ["Signature has expired."]
}
{
"non_field_errors": ["Error decoding signature."]
}
# Refresh-specific errors
{
"non_field_errors": ["Refresh has expired."]
}
{
"non_field_errors": ["orig_iat field is required."]
}Serializers depend on various JWT settings:
JWT_PAYLOAD_HANDLER: Function to generate JWT payloadsJWT_ENCODE_HANDLER: Function to encode JWT tokensJWT_DECODE_HANDLER: Function to decode JWT tokensJWT_PAYLOAD_GET_USERNAME_HANDLER: Function to extract username from payloadJWT_ALLOW_REFRESH: Enable/disable token refresh functionalityJWT_REFRESH_EXPIRATION_DELTA: Maximum time allowed for token refreshThese settings control the behavior of token generation, validation, and refresh operations within the serializers.
The serializers module defines several handler function references and the User model:
User = get_user_model()
"""Reference to the configured Django User model."""
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
"""Function reference for generating JWT payloads from user instances."""
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
"""Function reference for encoding JWT tokens."""
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
"""Function reference for decoding JWT tokens."""
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
"""Function reference for extracting username from JWT payload."""Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework-jwt