Security Middleware and HTTP Headers
The SecurityMiddleware in this codebase serves as a centralized mechanism for enforcing SSL and injecting security-related HTTP headers into responses. It is implemented in django/middleware/security.py and operates by intercepting both incoming requests (for redirection) and outgoing responses (for header injection).
SSL Redirection
The middleware handles the transition from unsecure HTTP to secure HTTPS through its process_request method. When SECURE_SSL_REDIRECT is enabled, the middleware checks if the incoming request is secure. If it is not, it issues an HttpResponsePermanentRedirect (HTTP 301) to the HTTPS version of the URL.
# django/middleware/security.py
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())
)
Redirection Configuration
SECURE_SSL_REDIRECT: A boolean that toggles the redirection logic.SECURE_SSL_HOST: If set, the middleware redirects to this specific host instead of the host provided in the request.SECURE_REDIRECT_EXEMPT: A list of regular expressions. If a request path matches any of these patterns, the redirection is skipped. Note that the middleware strips the leading slash from the path before matching.
HTTP Strict Transport Security (HSTS)
HSTS is implemented in the process_response method. It instructs browsers to only communicate with the server over HTTPS for a specified duration.
Crucially, the middleware only adds the Strict-Transport-Security header if the current request is already secure (request.is_secure()) and the header is not already present in the response.
# django/middleware/security.py
if (
self.sts_seconds
and request.is_secure()
and "Strict-Transport-Security" not in response
):
sts_header = "max-age=%s" % self.sts_seconds
if self.sts_include_subdomains:
sts_header += "; includeSubDomains"
if self.sts_preload:
sts_header += "; preload"
response.headers["Strict-Transport-Security"] = sts_header
HSTS Settings
SECURE_HSTS_SECONDS: Themax-agevalue in seconds. HSTS is only active if this is a non-zero integer.SECURE_HSTS_INCLUDE_SUBDOMAINS: IfTrue, appends theincludeSubDomainsdirective.SECURE_HSTS_PRELOAD: IfTrue, appends thepreloaddirective, allowing the site to be included in browser HSTS preload lists.
Content-Type Sniffing Prevention
To prevent browsers from guessing the MIME type of a response (which can lead to XSS attacks), the middleware can set the X-Content-Type-Options: nosniff header.
This is controlled by the SECURE_CONTENT_TYPE_NOSNIFF setting. The middleware uses response.headers.setdefault, ensuring it does not overwrite a header already set by a view or other middleware.
# django/middleware/security.py
if self.content_type_nosniff:
response.headers.setdefault("X-Content-Type-Options", "nosniff")
Modern Browser Policies
The middleware also manages headers that control how the browser shares information with other sites and how it isolates the application's execution environment.
Referrer Policy
The Referrer-Policy header governs how much referrer information is included with requests. The SECURE_REFERRER_POLICY setting supports multiple formats: a single string, a comma-separated string, or an iterable (list/tuple) of values.
# django/middleware/security.py
if self.referrer_policy:
response.headers.setdefault(
"Referrer-Policy",
",".join(
[v.strip() for v in self.referrer_policy.split(",")]
if isinstance(self.referrer_policy, str)
else self.referrer_policy
),
)
Cross-Origin Opener Policy (COOP)
The Cross-Origin-Opener-Policy header helps isolate the top-level document from other documents by ensuring they don't share a browsing context group. This is configured via the SECURE_CROSS_ORIGIN_OPENER_POLICY setting.
# django/middleware/security.py
if self.cross_origin_opener_policy:
response.setdefault(
"Cross-Origin-Opener-Policy",
self.cross_origin_opener_policy,
)
Integration and System Checks
The SecurityMiddleware is designed to be included in the MIDDLEWARE list in the project's settings. Django includes system checks (found in django/core/checks/security/base.py) that verify the middleware's presence when security settings are enabled.
For example, if SECURE_HSTS_SECONDS is set but SecurityMiddleware is missing from MIDDLEWARE, the system check will issue a warning because the HSTS header will not be applied.
# Example of testing this behavior in tests/middleware/test_security.py
@override_settings(SECURE_HSTS_SECONDS=600, SECURE_HSTS_INCLUDE_SUBDOMAINS=True)
def test_sts_include_subdomains(self):
response = self.process_response(secure=True)
self.assertEqual(
response.headers["Strict-Transport-Security"],
"max-age=600; includeSubDomains",
)
Key Implementation Details
- Non-Destructive: The middleware generally uses
setdefaultor checks for the existence of a header before adding it, allowing views to override security headers when necessary. - Order Matters: Because it handles redirection in
process_request, it is typically placed early in theMIDDLEWARElist to ensure unsecure requests are redirected before any heavy processing occurs.