Middleware

Built-in Middleware

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.

Built-in Middleware

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.

Security Middleware

SecurityMiddleware

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

What SecurityMiddleware Does:

# 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

Session Middleware

SessionMiddleware

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

Session Usage Examples:

# 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

Authentication Middleware

AuthenticationMiddleware

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()

CSRF Middleware

CsrfViewMiddleware

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

CSRF Protection Examples:

# 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

Common Middleware

CommonMiddleware

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

CommonMiddleware Features:

# 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

Message Middleware

MessageMiddleware

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'

Using Messages:

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

Clickjacking Middleware

XFrameOptionsMiddleware

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

Locale Middleware

LocaleMiddleware

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

Cache Middleware

UpdateCacheMiddleware and FetchFromCacheMiddleware

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()}"

Middleware Configuration Best Practices

Optimal Middleware Order

# 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',
]

Environment-Specific Configuration

# 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',
]

Testing Built-in Middleware

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')

Next Steps

Now that you understand Django's built-in middleware, let's learn how to create your own custom middleware to handle specific application requirements.