JSON Web Tokens (JWT) provide stateless, secure authentication for REST APIs. Combining JWT with custom middleware enables advanced security patterns—such as request validation, rate limiting, and role-based access control—at the middleware layer. This guide uses Django REST Framework (DRF) and djangorestframework-simplejwt
to implement robust JWT authentication and middleware-based security.
1. Install Dependencies
pip install djangorestframework djangorestframework-simplejwt
Ensure rest_framework
is in INSTALLED_APPS
in settings.py
.
2. Configure Simple JWT in Settings
# settings.py REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), } from datetime import timedelta 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',), }
3. Create Token Views
# urls.py from django.urls import path from rest_framework_simplejwt.views import ( TokenObtainPairView, TokenRefreshView, ) urlpatterns = [ path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ]
Clients POST credentials to /api/token/
to receive access
and refresh
tokens.
4. Implement Custom Security Middleware
Middleware can enforce additional checks on each request—for example, rate limiting or auditing.
# middleware/security.py import time from django.core.cache import cache from django.http import JsonResponse class RateLimitMiddleware: """ Simple per-user rate limiter using Django cache. Limits to 100 requests per 10-minute window. """ RATE_LIMIT = 100 WINDOW = 600 # seconds def __init__(self, get_response): self.get_response = get_response def __call__(self, request): user = request.user if user.is_authenticated: key = f"rl:{user.id}" count, first_ts = cache.get(key, (0, time.time())) now = time.time() if now - first_ts > self.WINDOW: count, first_ts = 0, now count += 1 cache.set(key, (count, first_ts), timeout=self.WINDOW) if count > self.RATE_LIMIT: return JsonResponse( {'detail': 'Rate limit exceeded.'}, status=429 ) return self.get_response(request)
Add the middleware to MIDDLEWARE
in settings.py
:
MIDDLEWARE = [ # ... 'middleware.security.RateLimitMiddleware', # ... ]
5. Role-Based Access Control Middleware
Example: restrict certain endpoints to users with an “admin” role stored in JWT claims.
# middleware/roles.py from django.http import JsonResponse from rest_framework_simplejwt.authentication import JWTAuthentication class RoleRequiredMiddleware: """ Ensure JWT contains a required role claim for protected paths. """ def __init__(self, get_response): self.get_response = get_response self.auth = JWTAuthentication() def __call__(self, request): # Only protect API endpoints under /api/admin/ if request.path.startswith('/api/admin/'): try: user, token = self.auth.authenticate(request) except Exception: return JsonResponse({'detail': 'Invalid or missing token.'}, status=401) role = token.payload.get('role') if role != 'admin': return JsonResponse({'detail': 'Admin role required.'}, status=403) request.user = user return self.get_response(request)
Register it after authentication middleware:
MIDDLEWARE = [ # ... 'rest_framework_simplejwt.authentication.JWTAuthentication', 'middleware.roles.RoleRequiredMiddleware', # ... ]
6. Add Custom JWT Claims in Token Generation
Override TokenObtainPairView
to include extra claims:
# views.py from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.serializers import TokenObtainPairSerializer class CustomTokenObtainPairSerializer(TokenObtainPairSerializer): @classmethod def get_token(cls, user): token = super().get_token(user) # Add custom claims token['role'] = user.profile.role # e.g., 'admin' or 'user' token['full_name'] = user.get_full_name() return token class CustomTokenObtainPairView(TokenObtainPairView): serializer_class = CustomTokenObtainPairSerializer
Update urls.py
to use the custom view:
from .views import CustomTokenObtainPairView urlpatterns = [ path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'), # ... ]
7. Test Authentication & Middleware
- Obtain JWT:
POST /api/token/ { "username": "admin", "password": "password123" }
- Access protected endpoint:
GET /api/admin/dashboard/ Authorization: Bearer <access_token>
- Rate-limit test: send 101 rapid requests to any endpoint as an authenticated user—expect HTTP 429 on the 101st.
8. Security Best Practices
- Rotate and blacklist refresh tokens to limit replay attacks.
- Use HTTPS to protect tokens in transit.
- Set
AUTH_TOKEN_CLASSES
to includeAccessToken
only for sensitive endpoints. - Monitor authentication events and rate limit violations in logs or an external monitoring system.