Skip to content
MDView as Markdown

Django

Django 6.0 brought CSP into core. Before that you needed django-csp; now the middleware, the settings and the template nonce all ship with the framework, and the report-only variant is a first-class setting rather than an afterthought.

What Django gives you

  • django.middleware.csp.ContentSecurityPolicyMiddleware, added to MIDDLEWARE.
  • Two mutually exclusive settings: SECURE_CSP to enforce, and SECURE_CSP_REPORT_ONLY to report without blocking.
  • Policies written as a dictionary of directives, using constants from django.utils.csp rather than hand-assembled strings.
  • CSP.NONCE in script-src or style-src, with the middleware generating a unique nonce per request.
  • The django.template.context_processors.csp context processor, exposing {{ csp_nonce }} to templates.

Setting the header

Start in report-only mode. Your URLs come from the Setup page:

from django.utils.csp import CSP
SECURE_CSP_REPORT_ONLY = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE, "'report-sha256'"],
    "report-uri": "https://abc123.report-uri.com/r/d/csp/reportOnly",
    "report-to": "default",
}
MIDDLEWARE = [
    "django.middleware.csp.ContentSecurityPolicyMiddleware",
    "yourapp.middleware.reporting_api_headers",
    # ...
]

Django has no setting for the two Reporting API headers, so a small middleware adds them:

# yourapp/middleware.py
def reporting_api_headers(get_response):
    def middleware(request):
        response = get_response(request)
        response["Reporting-Endpoints"] = 'default="https://abc123.report-uri.com/a/d/g"'
        response["Report-To"] = '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://abc123.report-uri.com/a/d/g"}],"include_subdomains":true}'
        return response
    return middleware

The Reporting-Endpoints and Report-To headers tell the browser where report-to default sends reports, including the CSP Integrity reports that 'report-sha256' turns on.

When you are ready to enforce, move the same dictionary to SECURE_CSP and switch the endpoint to the enforce one so the two data sets stay separate in your account.

Adding a nonce

Register the context processor:

TEMPLATES = [{
    "BACKEND": "django.template.backends.django.DjangoTemplates",
    "OPTIONS": {
        "context_processors": [
            "django.template.context_processors.csp",
        ],
    },
}]

Then use it on any inline script or style:

<script nonce="{{ csp_nonce }}">
  // allowed by the policy
</script>

Things to watch for

  • SECURE_CSP and SECURE_CSP_REPORT_ONLY are mutually exclusive. If you set both expecting a report-only shadow policy alongside an enforced one, you will not get it - run the report-only phase first, then switch.
  • Do not cache the HTML and the header together. A cached response serves a stale nonce to every visitor, which defeats the point entirely.
  • On Django 5.2 and earlier there is no built-in support; django-csp remains the route until you upgrade.

Official documentation

Django - How to use Django's Content Security Policy

Start Monitoring with Report URI

Already using us for CSP? Find your CSP reporting directives on Setup, or view your CSP reports.

New to Report URI? Create an account and grab your CSP reporting directives from the Setup page.

Start your free trial