Session middleware and the storage backends
coreintermediateSessionMiddleware is what makes request.session exist at all — it reads a session id from a cookie, loads that session's data via SESSION_ENGINE, and saves it back at the end of the request if it changed. The database backend (default) stores session data in the django_session table — durable, but a query per session load. The cache backend stores it in whatever CACHES backend is configured (fast, but ephemeral if that cache is ever cleared) — "cached_db" combines both, writing to the database but reading from cache first. The signed_cookies backend stores the session data itself in the cookie (cryptographically signed, not encrypted) — no server-side storage at all, but capped by the browser's ~4KB cookie limit and, being signed rather than encrypted, still readable (not just tamperable) by the client. SESSION_COOKIE_AGE sets expiration; session.flush() invalidates a session outright.
Think of it as
A session needs somewhere to actually live between requests, and Django deliberately makes that a swappable ENGINE (SESSION_ENGINE) rather than one fixed mechanism, because the right trade-off differs by project: the database backend is the safe default (durable, no extra infrastructure), the cache backend trades durability for speed (fine if losing sessions on a cache flush just means re-login, not data loss), and signed_cookies removes server-side storage entirely by putting the data itself in the cookie — which only works because Django signs it (SECRET_KEY-based, tamper-evident) though it is explicitly NOT encrypted, so anything sensitive stored in a signed-cookie session is still readable by the client, just not forgeable. Expiration and invalidation are two different concerns: SESSION_COOKIE_AGE (and SESSION_EXPIRE_AT_BROWSER_CLOSE) control how long a session naturally lives, while session.flush() is an explicit "kill this session right now" — the operation login()/logout() actually perform under the hood, which is why logging out invalidates a session rather than merely expiring it early.
What we're doing: Switch a high-traffic site from the default database backend to cached_db, and explicitly flush a session on a security-sensitive action.
- 2
- cached_db still writes through to the database on every save — switching engines is a read-path optimization, not a way to skip durability.
- 7
- flush() is stronger than just clearing session data — it also rotates the session key, the same mechanism login()/logout() use to prevent fixation.
Why this works: A password change is exactly the kind of event that should invalidate any existing session outright rather than let it continue — flush() (not just del request.session["..."]) is the correct call because it also generates a new session key, closing off any pre-existing session id an attacker might have already obtained.
Choosing signed_cookies for a session storing anything sensitive, assuming "signed" means "encrypted"
Wrong
Better
What you see: Nothing appears broken functionally — the signature check does prevent a TAMPERED cookie from being accepted — but any sensitive value stored in the session is directly visible to the client (and anyone with access to their browser/network capture) simply by base64-decoding the cookie, since signing proves authenticity, not secrecy.
Why: Django's own documentation is explicit that signed_cookies sessions are signed, not encrypted — the cryptographic signature stops a client from CHANGING the value undetected, but does nothing to stop them from READING it. Storing anything a user shouldn't be able to see (roles, internal flags, other users' data) in a signed-cookie session defeats the purpose even though tampering is still blocked.
- db / cache / cached_db
- Server-side storage
- db: durable, one query per load
- cache: fast, ephemeral; cached_db: both
- signed_cookies
- No server storage — data lives in the cookie
- Signed (tamper-evident), NOT encrypted
- Capped at ~4KB — never store sensitive data
Session storage backends
Together
Remember: SessionMiddleware makes request.session exist by reading/writing a cookie and delegating to SESSION_ENGINE (db by default; cache trades durability for speed; cached_db combines both; signed_cookies needs no server storage but is signed, NOT encrypted — never store anything sensitive there). SESSION_COOKIE_AGE alone is a fixed lifetime, not an idle timeout, unless paired with SESSION_SAVE_EVERY_REQUEST. session.flush() is the real invalidation call — it also rotates the session key.
See also: cookie security and csrf · login logout and authentication backends · the built in middleware stack

