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.