Skip to main content

Security and CSRF Protection in the Routing Layer

Security in the routing layer is managed by a suite of middleware that intercepts requests and responses to enforce safety protocols. These components protect against Cross-Site Request Forgery (CSRF), clickjacking, and insecure transport, ensuring that security policies are applied consistently before a request reaches a view and after a response is generated.

CSRF Protection

The CsrfViewMiddleware in django/middleware/csrf.py provides protection against CSRF attacks by ensuring that state-changing requests (like POST, PUT, or DELETE) contain a valid, non-predictable token.

Token Storage and Retrieval

The middleware supports two primary ways of storing the CSRF secret, controlled by the CSRF_USE_SESSIONS setting. The _get_secret method handles this logic:

def _get_secret(self, request):
if settings.CSRF_USE_SESSIONS:
try:
csrf_secret = request.session.get(CSRF_SESSION_KEY)
except AttributeError:
raise ImproperlyConfigured(
"CSRF_USE_SESSIONS is enabled, but request.session is not set..."
)
else:
try:
csrf_secret = request.COOKIES[settings.CSRF_COOKIE_NAME]
except KeyError:
csrf_secret = None
# ... logic to unmask the token if necessary ...
return csrf_secret

If CSRF_USE_SESSIONS is enabled, SessionMiddleware must appear before CsrfViewMiddleware in the MIDDLEWARE setting.

The Validation Lifecycle

CSRF validation occurs in the process_view method. The middleware follows a specific sequence to determine if a request is safe:

  1. Exemption Check: If the view is marked with csrf_exempt (via the @csrf_exempt decorator), validation is skipped.
  2. Safe Method Check: Requests using "safe" methods (GET, HEAD, OPTIONS, TRACE) are automatically accepted via _accept.
  3. Origin Verification: For all requests, if an HTTP_ORIGIN header is present, it is verified against settings.CSRF_TRUSTED_ORIGINS using _origin_verified.
  4. Strict Referer Check (HTTPS): For HTTPS requests, if the Origin header is missing, the middleware performs a strict check of the HTTP_REFERER header in _check_referer. This protects against Man-In-The-Middle (MITM) attacks that could otherwise bypass CSRF protections on secure connections.
  5. Token Verification: Finally, _check_token compares the token provided in the request (either in request.POST or the header defined by CSRF_HEADER_NAME) against the stored secret.

Token Masking

To mitigate SSL BREACH attacks, Django uses masked tokens. The _get_secret method unmasks the stored secret using _unmask_cipher_token, and the middleware ensures that the token sent to the client is scrambled differently on every request while still representing the same underlying secret.

Security Hardening and SSL

The SecurityMiddleware in django/middleware/security.py handles transport-level security and various browser-side security headers.

SSL Redirection

If SECURE_SSL_REDIRECT is enabled, the middleware intercepts non-HTTPS requests in process_request and returns an HttpResponsePermanentRedirect to the HTTPS version of the URL:

def process_request(self, request):
path = request.path.lstrip("/")
if (
self.redirect
and not request.is_secure()
and not any(pattern.search(path) for pattern in self.redirect_exempt)
):
host = self.redirect_host or request.get_host()
return HttpResponsePermanentRedirect(
"https://%s%s" % (host, request.get_full_path())
)

Security Headers

In process_response, the middleware injects several headers to harden the browser's security posture:

  • Strict-Transport-Security (HSTS): Controlled by SECURE_HSTS_SECONDS. It instructs the browser to only communicate with the server over HTTPS for a specified duration.
  • X-Content-Type-Options: If SECURE_CONTENT_TYPE_NOSNIFF is True, it sets this header to nosniff, preventing the browser from "sniffing" the content type and executing files as a different MIME type.
  • Referrer-Policy: Configured via SECURE_REFERRER_POLICY to control how much information is included in the Referer header when navigating away from the site.
  • Cross-Origin-Opener-Policy (COOP): Configured via SECURE_CROSS_ORIGIN_OPENER_POLICY to isolate the browsing context from other windows.

Clickjacking Protection

The XFrameOptionsMiddleware in django/middleware/clickjacking.py prevents the site from being rendered inside an <iframe_>, <frame_>, or <object_> on other sites, which is a common vector for clickjacking attacks.

By default, the middleware sets the X-Frame-Options header to DENY. This behavior can be customized via the X_FRAME_OPTIONS setting (e.g., to SAMEORIGIN).

def process_response(self, request, response):
# Don't set it if it's already in the response or exempt
if response.get("X-Frame-Options") is not None:
return response
if getattr(response, "xframe_options_exempt", False):
return response

response.headers["X-Frame-Options"] = self.get_xframe_options_value(request, response)
return response

Granular Control with Decorators

While middleware provides global protection, specific views often require different security configurations. Django provides decorators in django/views/decorators/csrf.py and django/views/decorators/clickjacking.py to modify middleware behavior per-view.

  • @csrf_exempt: Marks a view to skip the CsrfViewMiddleware check. This is often used for APIs that handle their own authentication (like Webhooks).
  • @csrf_protect: Forces CSRF protection on a view, even if the middleware is not globally enabled.
  • @xframe_options_exempt: Prevents the XFrameOptionsMiddleware from setting the header, allowing the view to be framed.
  • @xframe_options_sameorigin: Overrides the global policy to allow the view to be framed only by pages on the same domain.

These decorators work by setting attributes on the view function (e.g., view_func.csrf_exempt = True), which the middleware then inspects during the process_view or process_response phases.