Skip to main content

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:

  1. Origin Verification: If an HTTP_ORIGIN header is present, _origin_verified checks it against the current host and settings.CSRF_TRUSTED_ORIGINS.
  2. Strict Referer Checking: For HTTPS requests, if the Origin header is missing, the middleware performs strict Referer checking 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.
  3. Token Matching: Finally, _check_token extracts the token from the POST data (csrfmiddlewaretoken) or the header defined in settings.CSRF_HEADER_NAME (defaulting to X-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 of CSRF_ALLOWED_CHARS. This is used internally by _check_token_format to 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_REFERER or REASON_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_SESSIONS is enabled, the middleware stores the secret in request.session[CSRF_SESSION_KEY]. This provides higher security by keeping the secret server-side but requires SessionMiddleware to be processed before CsrfViewMiddleware.
  • Trusted Origins: The CSRF_TRUSTED_ORIGINS setting supports subdomains (e.g., *.example.com). The middleware pre-calculates these in allowed_origin_subdomains using cached_property to 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.