Django comes with several built-in middleware components that handle common web application concerns. Understanding these middleware components helps you leverage Django's capabilities and serves as examples for creating your own middleware.
Provides several security enhancements for your application:
# django.middleware.security.SecurityMiddleware
# Automatic configuration in settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # Should be first
# ... other middleware
]
# Security settings it respects:
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_REDIRECT_EXEMPT = []
SECURE_REFERRER_POLICY = 'same-origin'
SECURE_SSL_HOST = None
SECURE_SSL_REDIRECT = True
# Example of headers added by SecurityMiddleware
"""
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Referrer-Policy: same-origin
"""
# Custom implementation example
class CustomSecurityMiddleware:
"""Custom security middleware example"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# HTTPS redirect
if not request.is_secure() and not settings.DEBUG:
return HttpResponsePermanentRedirect(
'https://' + request.get_host() + request.get_full_path()
)
response = self.get_response(request)
# Add security headers
response['X-Content-Type-Options'] = 'nosniff'
response['X-Frame-Options'] = 'DENY'
response['X-XSS-Protection'] = '1; mode=block'
# HSTS header for HTTPS
if request.is_secure():
response['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
Enables session support across requests:
# django.contrib.sessions.middleware.SessionMiddleware
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
# ... other middleware
]
# Session configuration
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # Default
SESSION_COOKIE_AGE = 1209600 # 2 weeks
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_NAME = 'sessionid'
SESSION_COOKIE_PATH = '/'
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = False # Set to True in production with HTTPS
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_SAVE_EVERY_REQUEST = False
# In views, after SessionMiddleware is enabled
def my_view(request):
# Store data in session
request.session['user_preference'] = 'dark_mode'
request.session['cart_items'] = ['item1', 'item2']
# Retrieve data from session
preference = request.session.get('user_preference', 'light_mode')
cart = request.session.get('cart_items', [])
# Session methods
request.session.set_expiry(300) # Expire in 5 minutes
request.session.flush() # Delete session data and regenerate key
return HttpResponse(f"Preference: {preference}")
# Custom session middleware example
class CustomSessionMiddleware:
"""Custom session handling"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Load session before view
session_key = request.COOKIES.get('sessionid')
if session_key:
# Load session data
request.session = self.load_session(session_key)
else:
request.session = {}
response = self.get_response(request)
# Save session after view
if hasattr(request, 'session') and request.session:
session_key = self.save_session(request.session)
response.set_cookie('sessionid', session_key)
return response
Adds the user attribute to requests:
# django.contrib.auth.middleware.AuthenticationMiddleware
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', # Required first
'django.contrib.auth.middleware.AuthenticationMiddleware',
# ... other middleware
]
# Usage in views
def my_view(request):
if request.user.is_authenticated:
return HttpResponse(f"Hello, {request.user.username}!")
else:
return HttpResponse("Please log in.")
# Custom authentication middleware
class CustomAuthMiddleware:
"""Custom authentication middleware"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Add user to request
user = self.get_user(request)
request.user = user
response = self.get_response(request)
return response
def get_user(self, request):
"""Get user from session or return AnonymousUser"""
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth import get_user_model
User = get_user_model()
try:
user_id = request.session.get('_auth_user_id')
if user_id:
return User.objects.get(pk=user_id)
except (User.DoesNotExist, KeyError):
pass
return AnonymousUser()
Protects against Cross-Site Request Forgery attacks:
# django.middleware.csrf.CsrfViewMiddleware
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
# ... other middleware
]
# CSRF settings
CSRF_COOKIE_AGE = 31449600 # 1 year
CSRF_COOKIE_DOMAIN = None
CSRF_COOKIE_HTTPONLY = False # Must be False for JavaScript access
CSRF_COOKIE_NAME = 'csrftoken'
CSRF_COOKIE_PATH = '/'
CSRF_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = False # Set to True in production with HTTPS
CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure'
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
CSRF_TRUSTED_ORIGINS = []
CSRF_USE_SESSIONS = False
# In templates
"""
<form method="post">
{% csrf_token %}
<!-- form fields -->
</form>
"""
# In JavaScript (AJAX)
"""
// Get CSRF token from cookie
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// Use in AJAX requests
fetch('/api/endpoint/', {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
"""
# Exempting views from CSRF protection
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def api_endpoint(request):
# This view is exempt from CSRF protection
return JsonResponse({'status': 'ok'})
# Custom CSRF middleware example
class CustomCSRFMiddleware:
"""Custom CSRF protection"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.method in ('POST', 'PUT', 'PATCH', 'DELETE'):
if not self.is_csrf_valid(request):
return HttpResponseForbidden('CSRF token missing or invalid')
response = self.get_response(request)
# Set CSRF token cookie
if not request.COOKIES.get('csrftoken'):
csrf_token = self.generate_csrf_token()
response.set_cookie('csrftoken', csrf_token)
return response
def is_csrf_valid(self, request):
"""Validate CSRF token"""
token_from_cookie = request.COOKIES.get('csrftoken')
token_from_header = request.META.get('HTTP_X_CSRFTOKEN')
return token_from_cookie and token_from_cookie == token_from_header
Provides several common features:
# django.middleware.common.CommonMiddleware
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
# ... other middleware
]
# Settings it respects
APPEND_SLASH = True
PREPEND_WWW = False
DISALLOWED_USER_AGENTS = []
USE_ETAGS = False
# What CommonMiddleware does:
# 1. URL rewriting (APPEND_SLASH)
# /example → /example/ (if APPEND_SLASH=True and /example/ exists)
# 2. WWW subdomain handling
# example.com → www.example.com (if PREPEND_WWW=True)
# 3. User agent blocking
DISALLOWED_USER_AGENTS = [
re.compile(r'BadBot'),
re.compile(r'Scraper'),
]
# 4. ETags for caching
# Automatically adds ETag headers based on response content
# Custom common middleware example
class CustomCommonMiddleware:
"""Custom common functionality"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# URL normalization
if settings.APPEND_SLASH and not request.path.endswith('/'):
# Check if URL with slash exists
new_path = request.path + '/'
if self.url_exists(new_path):
return HttpResponsePermanentRedirect(new_path)
# Block disallowed user agents
user_agent = request.META.get('HTTP_USER_AGENT', '')
if self.is_disallowed_user_agent(user_agent):
return HttpResponseForbidden('User agent not allowed')
response = self.get_response(request)
# Add ETag header
if settings.USE_ETAGS:
etag = self.calculate_etag(response.content)
response['ETag'] = etag
return response
Enables the Django messages framework:
# django.contrib.messages.middleware.MessageMiddleware
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', # Required
'django.contrib.messages.middleware.MessageMiddleware',
# ... other middleware
]
# Messages configuration
from django.contrib.messages import constants as messages
MESSAGE_TAGS = {
messages.DEBUG: 'debug',
messages.INFO: 'info',
messages.SUCCESS: 'success',
messages.WARNING: 'warning',
messages.ERROR: 'error',
}
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
from django.contrib import messages
def my_view(request):
# Add messages
messages.debug(request, 'Debug message')
messages.info(request, 'Info message')
messages.success(request, 'Success message')
messages.warning(request, 'Warning message')
messages.error(request, 'Error message')
return render(request, 'template.html')
# In templates
"""
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
"""
# Custom message middleware
class CustomMessageMiddleware:
"""Custom message handling"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Initialize messages storage
request._messages = self.get_messages_storage(request)
response = self.get_response(request)
# Process messages for response
if hasattr(request, '_messages'):
# Add messages to response context or headers
messages_data = list(request._messages)
if messages_data:
response['X-Messages-Count'] = str(len(messages_data))
return response
Protects against clickjacking attacks:
# django.middleware.clickjacking.XFrameOptionsMiddleware
MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# ... other middleware
]
# Configuration
X_FRAME_OPTIONS = 'DENY' # or 'SAMEORIGIN' or 'ALLOW-FROM uri'
# Per-view configuration
from django.views.decorators.clickjacking import xframe_options_deny, xframe_options_sameorigin
@xframe_options_deny
def sensitive_view(request):
return render(request, 'sensitive.html')
@xframe_options_sameorigin
def embeddable_view(request):
return render(request, 'embeddable.html')
# Custom clickjacking protection
class CustomClickjackingMiddleware:
"""Custom clickjacking protection"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Add X-Frame-Options header
if not response.get('X-Frame-Options'):
response['X-Frame-Options'] = 'DENY'
# Add Content Security Policy
csp = "frame-ancestors 'none'"
if response.get('Content-Security-Policy'):
response['Content-Security-Policy'] += f'; {csp}'
else:
response['Content-Security-Policy'] = csp
return response
Enables internationalization based on request data:
# django.middleware.locale.LocaleMiddleware
MIDDLEWARE = [
'django.middleware.locale.LocaleMiddleware',
# ... other middleware
]
# I18n configuration
USE_I18N = True
USE_L10N = True
LANGUAGE_CODE = 'en-us'
LANGUAGES = [
('en', 'English'),
('es', 'Spanish'),
('fr', 'French'),
]
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
# Custom locale middleware
class CustomLocaleMiddleware:
"""Custom locale detection"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Detect language from various sources
language = self.get_language_from_request(request)
# Activate language
from django.utils import translation
translation.activate(language)
request.LANGUAGE_CODE = language
response = self.get_response(request)
# Deactivate language
translation.deactivate()
return response
def get_language_from_request(self, request):
"""Detect language from request"""
# 1. Check URL parameter
if 'lang' in request.GET:
return request.GET['lang']
# 2. Check session
if hasattr(request, 'session') and 'language' in request.session:
return request.session['language']
# 3. Check Accept-Language header
accept_language = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
# Parse and return best match
# 4. Return default
return settings.LANGUAGE_CODE
Provides full-page caching:
# Cache middleware (order matters!)
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # First
# ... other middleware
'django.middleware.cache.FetchFromCacheMiddleware', # Last
]
# Cache configuration
CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 600 # 10 minutes
CACHE_MIDDLEWARE_KEY_PREFIX = ''
# Custom cache middleware
class CustomCacheMiddleware:
"""Custom caching logic"""
def __init__(self, get_response):
self.get_response = get_response
from django.core.cache import cache
self.cache = cache
def __call__(self, request):
# Try to get cached response
cache_key = self.get_cache_key(request)
cached_response = self.cache.get(cache_key)
if cached_response and not settings.DEBUG:
return cached_response
response = self.get_response(request)
# Cache successful responses
if response.status_code == 200 and request.method == 'GET':
self.cache.set(cache_key, response, 300) # 5 minutes
return response
def get_cache_key(self, request):
"""Generate cache key for request"""
return f"page_cache:{request.path}:{request.GET.urlencode()}"
# Recommended middleware order
MIDDLEWARE = [
# Security (should be first)
'django.middleware.security.SecurityMiddleware',
# Caching (early for performance)
'django.middleware.cache.UpdateCacheMiddleware',
# Core functionality
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Internationalization
'django.middleware.locale.LocaleMiddleware',
# Security (clickjacking)
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# Custom middleware
'myapp.middleware.CustomMiddleware',
# Caching (should be last)
'django.middleware.cache.FetchFromCacheMiddleware',
]
# settings/base.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# settings/development.py
MIDDLEWARE = MIDDLEWARE + [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'myapp.middleware.DevelopmentMiddleware',
]
# settings/production.py
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
] + MIDDLEWARE + [
'myapp.middleware.ProductionLoggingMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
from django.test import TestCase, RequestFactory
from django.middleware.common import CommonMiddleware
from django.http import HttpResponse
class BuiltinMiddlewareTests(TestCase):
"""Test built-in middleware functionality"""
def setUp(self):
self.factory = RequestFactory()
def test_common_middleware_append_slash(self):
"""Test CommonMiddleware APPEND_SLASH functionality"""
def get_response(request):
return HttpResponse()
middleware = CommonMiddleware(get_response)
# Test URL without trailing slash
request = self.factory.get('/test')
response = middleware(request)
# Should redirect to URL with slash
self.assertEqual(response.status_code, 301)
self.assertTrue(response.url.endswith('/'))
def test_security_middleware_headers(self):
"""Test SecurityMiddleware adds security headers"""
from django.middleware.security import SecurityMiddleware
def get_response(request):
return HttpResponse()
middleware = SecurityMiddleware(get_response)
request = self.factory.get('/test/')
with self.settings(
SECURE_CONTENT_TYPE_NOSNIFF=True,
SECURE_BROWSER_XSS_FILTER=True
):
response = middleware(request)
self.assertEqual(response['X-Content-Type-Options'], 'nosniff')
self.assertEqual(response['X-XSS-Protection'], '1; mode=block')
Now that you understand Django's built-in middleware, let's learn how to create your own custom middleware to handle specific application requirements.
Middleware Overview
Django middleware is a framework of hooks into Django's request/response processing. It's a light, low-level "plugin" system for globally altering Django's input or output. This chapter provides a comprehensive understanding of how middleware works and its role in Django applications.
Creating Custom Middleware
Custom middleware allows you to implement application-specific functionality that runs for every request. This chapter covers designing, implementing, and testing custom middleware for various use cases.