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_CLASSESto includeAccessTokenonly for sensitive endpoints. - Monitor authentication events and rate limit violations in logs or an external monitoring system.
