Session and CSRF cookie flags
Django sets two cookies that matter. Read straight from global_settings.py:
| Setting | Default | Should be |
|---|---|---|
SESSION_COOKIE_SECURE |
False |
True |
CSRF_COOKIE_SECURE |
False |
True |
SESSION_COOKIE_HTTPONLY |
True |
leave it |
CSRF_COOKIE_HTTPONLY |
False |
leave it — see below |
SESSION_COOKIE_SAMESITE |
"Lax" |
usually leave it |
CSRF_COOKIE_SAMESITE |
"Lax" |
usually leave it |
Both Secure flags default to False because Django cannot know whether your
development server speaks HTTPS. Both SameSite values already default to Lax, so
that half needs no action.
SESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = Truecurl -sI https://example.com/ | grep -i set-cookieCheck the real response rather than the setting — behind a proxy this is exactly where
the two disagree. If Secure is missing here while the setting is True, the problem
is the proxy hop, not Django. See
SSL redirect behind a proxy.
You have 'django.middleware.csrf.CsrfViewMiddleware' in your MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE to True. Using a secure-only CSRF cookie makes it more difficult for network traffic sniffers to steal the CSRF token.The CSRF cookie may be transmitted over plaintext HTTP. Fires only when the middleware is enabled and CSRF_USE_SESSIONS is off.
Read in: django/core/checks/security/csrf.py — id security.W016Why CSRF_COOKIE_HTTPONLY is False on purpose
Section titled “Why CSRF_COOKIE_HTTPONLY is False on purpose”This looks like an oversight and gets “fixed” in a lot of hardening guides. It is
deliberate, and setting it to True buys close to nothing while breaking things.
The CSRF cookie is not a credential. It is one half of a matched pair — the defence
works because an attacker’s page cannot read the cookie to copy its value into the
form, and the same-origin policy already prevents that. HttpOnly blocks JavaScript
access, but the JavaScript that legitimately needs the token is your own: any AJAX
request has to read csrftoken to set the X-CSRFToken header.
So CSRF_COOKIE_HTTPONLY = True breaks standard AJAX patterns and does not stop an
attacker who has script execution — at that point they can simply read the token out
of the DOM instead.
The session cookie is a credential, which is why SESSION_COOKIE_HTTPONLY defaults
to True and should stay there.
The two ages are wildly different
Section titled “The two ages are wildly different”SESSION_COOKIE_AGE is two weeks. CSRF_COOKIE_AGE is one year. Both read from
source. If you want the CSRF token to expire with the session, set
CSRF_USE_SESSIONS = True, which stores the token in the session instead of a cookie
and makes CSRF_COOKIE_* irrelevant.
SESSION_COOKIE_SECURE = True means the cookie is not sent over plain HTTP at all —
so a local runserver on http://127.0.0.1:8000 cannot log in. Gate both flags on
the same environment switch that drives DEBUG rather than hardcoding them, or local
development stops working in a way that looks like a broken login form.
Tightening SameSite from Lax to Strict breaks inbound links from other sites:
the cookie is withheld even on a top-level navigation, so a user following a link from
an email arrives logged out. Lax is the right default for almost every site.
Related
Section titled “Related”- SECRET_KEY — what signs the session cookie
- SSL redirect behind a proxy — why
Securecan vanish behind a proxy