CSRF Protection
The CSRF protection mechanism in this codebase is centered around the CsrfViewMiddleware, which implements a "Double Submit Cookie" pattern enhanced with secret masking and strict origin verification. This design ensures that malicious sites cannot perform actions on behalf of a user by exploiting the browser's automatic inclusion of cookies in cross-site requests.
The Secret and the Token
A fundamental design choice in django.middleware.csrf is the distinction between the CSRF secret and the CSRF token.
- The Secret: This is a persistent, random string (defined by
CSRF_SECRET_LENGTH = 32) stored either in a cookie or the user's session. It serves as the ground truth for the user's identity in the context of CSRF. - The Token: This is what is actually sent in HTML forms or HTTP headers. To mitigate SSL BREACH attacks—where an attacker might guess the secret by observing changes in compressed HTTP responses—the middleware "masks" the secret.
The _mask_cipher_secret function generates a new random mask for every token and XORs it with the secret. When a request arrives, _unmask_cipher_token reverses this process. This means that even if the underlying secret remains the same, the token sent to the client changes with every request.
# django/middleware/csrf.py
def _mask_cipher_secret(secret):
mask = _get_new_csrf_string()
chars = CSRF_ALLOWED_CHARS
pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in mask))
cipher = "".join(chars[(x + y) % len(chars)] for x, y in pairs)
return mask + cipher
Middleware Lifecycle
The CsrfViewMiddleware orchestrates protection across three phases of the request-response cycle:
1. Request Initialization (process_request)
The middleware first attempts to retrieve the existing secret using _get_secret. If the secret is missing or malformed (raising InvalidTokenFormat), it generates a new one via _add_new_csrf_cookie. This ensures that a secret is always available in request.META["CSRF_COOKIE"] for subsequent view processing or template rendering.
2. View Validation (process_view)
This is the core enforcement phase. The middleware skips validation for "safe" methods (GET, HEAD, OPTIONS, TRACE) as defined by RFC 9110, assuming these methods do not trigger side effects. It also respects the csrf_exempt attribute, which can be applied to views via decorators.
For unsafe methods, the middleware performs a sequence of checks:
- Origin Verification: If an
HTTP_ORIGINheader is present,_origin_verifiedchecks it against the current host andsettings.CSRF_TRUSTED_ORIGINS. - Strict Referer Checking: For HTTPS requests, if the
Originheader is missing, the middleware performs strictRefererchecking in_check_referer. This is a critical defense against Man-In-The-Middle (MITM) attacks where an attacker on an HTTP site might attempt to POST to an HTTPS site on the same domain. - Token Matching: Finally,
_check_tokenextracts the token from the POST data (csrfmiddlewaretoken) or the header defined insettings.CSRF_HEADER_NAME(defaulting toX-CSRFToken).
3. Response Finalization (process_response)
If the CSRF secret was newly created or rotated during the request, process_response ensures the updated secret is sent back to the client via _set_csrf_cookie.
Security Exceptions
The middleware uses two specific exception classes to manage validation failures:
InvalidTokenFormat: Raised when a token does not meet the required length or contains characters outside ofCSRF_ALLOWED_CHARS. This is used internally by_check_token_formatto distinguish between a policy violation and a malformed input.RejectRequest: A higher-level exception used to signal that a request should be blocked. It carries a reason (e.g.,REASON_NO_REFERERorREASON_BAD_ORIGIN) which is logged and passed to the failure view.
When a RejectRequest occurs, the middleware calls _reject, which invokes the view defined in settings.CSRF_FAILURE_VIEW to return a 403 Forbidden response.
Configuration Tradeoffs
The implementation allows for significant flexibility through settings:
- Storage: By default, the secret is stored in a cookie. However, if
CSRF_USE_SESSIONSis enabled, the middleware stores the secret inrequest.session[CSRF_SESSION_KEY]. This provides higher security by keeping the secret server-side but requiresSessionMiddlewareto be processed beforeCsrfViewMiddleware. - Trusted Origins: The
CSRF_TRUSTED_ORIGINSsetting supports subdomains (e.g.,*.example.com). The middleware pre-calculates these inallowed_origin_subdomainsusingcached_propertyto ensure efficient lookups during the request flow.
# Example of how the middleware checks subdomains
# django/middleware/csrf.py
def _origin_verified(self, request):
# ... (exact matches)
return any(
is_same_domain(parsed_origin_netloc, host)
for host in self.allowed_origin_subdomains.get(parsed_origin_scheme, ())
)
This multi-layered approach—combining origin verification, referer checking for secure connections, and masked token comparison—provides a robust defense against CSRF while maintaining compatibility with AJAX and standard HTML forms.