Clickjacking protection
X_FRAME_OPTIONS defaults to "DENY" in global_settings.py. It defaulted to
"SAMEORIGIN" before Django 3.0, which is why a large amount of otherwise-current
advice tells you to set a value that is now weaker than the default.
Rated low deliberately. The header is worth having and costs nothing, but on a site
with no framing-sensitive UI the exposure it closes is small — and frame-ancestors
in a Content Security Policy supersedes it on
every browser that matters.
MIDDLEWARE = [ "django.middleware.clickjacking.XFrameOptionsMiddleware", # ...]
# X_FRAME_OPTIONS = "DENY" is already the default — do not set SAMEORIGIN# unless something genuinely frames your own pages.curl -sI https://example.com/ | grep -i x-frame-optionsYou do not have 'django.middleware.clickjacking.XFrameOptionsMiddleware' in your MIDDLEWARE, so your pages will not be served with an 'x-frame-options' HTTP header. Unless there is a good reason for your site to be served in a frame, you should consider enabling this header to help prevent clickjacking attacks.The middleware is absent, so X_FRAME_OPTIONS is being read and ignored. This is the actual finding on most projects.
Read in: django/core/checks/security/base.py — id security.W002You have 'django.middleware.clickjacking.XFrameOptionsMiddleware' in your MIDDLEWARE, but X_FRAME_OPTIONS is not set to 'DENY'.Someone set SAMEORIGIN explicitly. Worth checking whether the reason still exists.
Read in: django/core/checks/security/base.py — id security.W019The finding is the middleware, not the setting
Section titled “The finding is the middleware, not the setting”Both warnings are about the same failure in different directions. W002 means the
setting is inert because the middleware is missing — the common case, since the
setting looks configured in settings.py and produces nothing on the wire. W019
means someone deliberately chose SAMEORIGIN.
If you need framing for one view rather than the whole site, use the decorators instead of weakening the global value:
from django.views.decorators.clickjacking import xframe_options_sameorigin
@xframe_options_sameorigindef embeddable_widget(request): ...DENY breaks anything that frames your pages, including your own — embedded preview
panes, a legacy admin that iframes a report, an OAuth consent screen shown in a frame.
Those break with a blank frame and a console message rather than a server error, so
they are easy to miss until someone reports it.
Prefer per-view decorators over lowering the site-wide value.
Related
Section titled “Related”- Content Security Policy —
frame-ancestorsis the modern control - Security headers — the rest of the header set