DjangoCon Europe 2019: Django and Web Security Headers

Writeup of the DjangoCon Europe 2019 talk »Django and Web Security Headers« by Adam Johnson

Adam Johnson: I started programming when I was eight years old in good ol’ Quickbasic and have been hooked since. I’ve worked with Django since 2012, and have been a Django core contributor since 2016.

The web is a platform with a lot of backwards compatibility concerns and all browsers are trying to keep all the websites working for all time, be they modern HTTP2, or ancient HTTP. Since old stuff hangs around, the one thing that is okay to add and upgrade, are http headers.

There is a page to check your headers, and also the Mozilla observatory – check them out in any case, since you'll learn something about your page.


XSS stands for Cross-Site-Scripting (including code from different domains, mostly referring to malicious usage). Most browsers have XSS auditors on by default. They are not entirely backwards compatible, but they'll stop that one request.

If you set the mode to block via your header, though, the XSS auditor will block the entire page.

In Django: set SECURE_BROWSER_XSS_FILTER = True and include the SecurityMiddleware in your middlewares.


You should serve your site over HTTPS. Most websites are HTTPS, but problems can occur if your browser asks for HTTP first, since the request/redirect before the upgrade will be insecure. This header tells your browser to never use HTTP with that page ever again (within the limits of the timeout). There is also a huge list of domains with this enabled, to prevent any HTTP requests going out.

In Django: set SECURE_HSTS_SECONDS to the timeout if the SecurityMiddleware is active. Please ramp up the seconds gradually – only enable includeSubdomains and preload when you're really sure. If you break your HTTPS after having set this flag, any browser that has seen is will not be able to see your website again.


Browsers guess content type with "MIME Sniffing". This backfires when user uploaded images are interpreted as HTML. You can opt out by sending the header value nosniff.



Clickjacking embeds another webpage as an iframe, and the header tells browser to never embed the page (or only from okay pages).

In Django: X_FRAME_OPTIONS = 'DENY' (or use whitelisting), and include the XFrameOptionsMiddleware.


Browsers pass the Referer (with typo) with every request, telling the page where they came from. But this leaks information – both personal information, and metadata. This header controls who gets this information.

In Django: Install django-referrer-policy, add its ReferrerPolicyMiddleware, and set REFERRER_POLICY = 'same-origin' or other values.


This header stops your page from including content from any site. It's the strongest way to prevent XSS, clickjacking, and others. It's a huge complex topic. The header contains ; separated directives, like these:

Read about strict mode here, or consider starting with reporting mode to help with the roll-out in existing pages.

default-src 'self';  # by default only from this domain
img-src *;  # images from anywhere
script-src  # scripts only from here

In Django: Mozilla provides django-csp, add CSPMiddleware, and then set a couple of values, e.g. CSP_DEFAULT_SRC = 'self'.


New and not yet supported in all browsers – this header lets you disable browser feature, like autoplay, geolocation, camera, etc.

In Django: Install django-feature-policy, add FeaturePolicyMiddleware and set FEATURE_POLICY.