Django cookbook · Intermediate

Harden security settings, including a Content Security Policy

The app passes development checks but ships without HTTPS enforcement, secure cookies or a CSP.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Enable HTTPS redirects and HSTS, secure cookies, and Django 6's built-in Content Security Policy middleware with nonces for inline scripts. Verify with manage.py check --deploy.

Solution

# settings/production.py
from django.utils.csp import CSP

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")   # only behind a proxy that sets it
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 60 * 60 * 24 * 365
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.middleware.csp.ContentSecurityPolicyMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

SECURE_CSP = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE],                 # <script nonce="{{ csp_nonce }}">
    "img-src": [CSP.SELF, "data:", "https://cdn.example.com"],
    "frame-ancestors": [CSP.NONE],
}

Pitfalls

  • Setting SECURE_PROXY_SSL_HEADER when no trusted proxy sets the header (clients could spoof it).
  • Turning on long HSTS with preload before every subdomain serves HTTPS.
  • Allowing 'unsafe-inline' scripts in the CSP, which removes most of its XSS protection.

Variations

  • Start with SECURE_CSP_REPORT_ONLY to collect violations before enforcing.
  • Add the csp context processor and use {{ csp_nonce }} on inline scripts.

Further reading: Content Security Policy in Django

Reference: https://docs.djangoproject.com/en/6.0/ref/csp/

More Django recipes

Was this page helpful?