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 toMIDDLEWARE.- Two mutually exclusive settings:
SECURE_CSPto enforce, andSECURE_CSP_REPORT_ONLYto report without blocking. - Policies written as a dictionary of directives, using constants from
django.utils.csprather than hand-assembled strings. CSP.NONCEinscript-srcorstyle-src, with the middleware generating a unique nonce per request.- The
django.template.context_processors.cspcontext 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_CSPandSECURE_CSP_REPORT_ONLYare 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-cspremains 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.