skip to content

Secret Key Lifecycle

SECRET_KEY signs sessions, messages, password-reset tokens and signing-API output; SECRET_KEY_FALLBACKS lets you rotate it. Interviewers ask what breaks when the key changes or leaks.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

3

In Django, what does SECRET_KEY actually sign, and what happens to your users when you change it without a fallback?

level: middleimportance: must knowfreq 55%

answer

  1. HMAC, not encryption
  2. sessions and the auth hash
  3. cookie messages, reset links
  4. passwords are untouched

basics

~20 s

Django's SECRET_KEY keys the HMAC signatures on session data, the session auth hash, cookie-stored messages, password-reset tokens and django.core.signing output. Changing it without a fallback logs everyone out and voids those tokens; password hashes are unaffected.

solid answer

~40 s

`SECRET_KEY` is the default key for Django's cryptographic **signing**, not an encryption key. It signs session data for every backend except the pure `cache` one, and it keys the **session auth hash**, an HMAC of the user's password field that `get_user()` checks on each request. It also signs messages in `CookieStorage`/`FallbackStorage`, `PasswordResetView` tokens, `set_signed_cookie()` values and any `signing.dumps()` payload that doesn't pass its own key. Change it without moving the old value to `SECRET_KEY_FALLBACKS` and every one of those fails verification: sessions decode as empty and the auth hash mismatches, so all users are logged out, pending reset links and signed links die, and cookie messages vanish. Password hashes are salted per user and never involve the key, so passwords keep working.

code

python · 9 lines
python
from django.core import signing

token = signing.dumps({"user_id": 42}, salt="unsubscribe")

# Later, possibly after a deploy that changed SECRET_KEY:
try:
    data = signing.loads(token, salt="unsubscribe", max_age=60 * 60 * 24 * 7)
except signing.BadSignature:
    data = None  # key changed without a fallback, or the token was tampered with

go deeper

for a junior

Know that SECRET_KEY signs sessions and reset links, must stay secret, and that changing it logs everyone out but leaves passwords alone.

for a middle

List the documented uses, explain the session auth hash, and say why CSRF tokens and password hashes do not depend on the key.

for a senior

Plan a key change as a user-visible event: fallbacks, consistent keys across servers, separate keys per environment, and the app's own signed links.

for a principal

Decide which signed artefacts deserve their own keys rather than the shared SECRET_KEY, so one rotation does not break unrelated flows.

## Signing, not encryption **`SECRET_KEY`** is a per-installation secret whose job is to make **HMAC signatures**: a server computes a keyed hash over some data, attaches it, and later recomputes it to prove the data has not been changed and was produced by someone holding the key. The data itself is readable: a signed cookie's value is visible to the user. The documentation's own list of uses is short, and interviewers like candidates who know it and know what is *not* on it. ## What the key signs | Use | Where | Effect of losing the old key | |---|---|---| | **Session data** | the `db`, `file` and `cached_db` backends sign the serialized session through `SessionBase.encode()`; `signed_cookies` signs the whole session into the cookie; only the pure `cache` backend skips it | the session loads as empty (the encode-based backends also log to `django.security.SuspiciousSession`) | | **Session auth hash** | `AbstractBaseUser.get_session_auth_hash()` is an HMAC of the password field, stored in the session at login | `get_user()` sees a mismatch and flushes the session, even on the `cache` backend | | **Messages** | `CookieStorage` and the default `FallbackStorage` sign the messages cookie | pending flash messages are discarded | | **Password-reset tokens** | `PasswordResetTokenGenerator` uses `SECRET_KEY` as its secret | every emailed reset link becomes invalid | | **Signing API** | `django.core.signing.dumps()`, `Signer`, `TimestampSigner`, `HttpResponse.set_signed_cookie()` default to the key | your own signed links and cookies fail with `BadSignature` | The session auth hash is the reason a key change logs everyone out regardless of backend: even where the session data is not signed, the hash stored at login no longer matches the one recomputed with the new key. ## What the key does not touch - **Password hashes.** Hashers such as PBKDF2 use a random per-user salt stored in the hash string. The documentation states that secret keys are not used for passwords and rotation will not affect them. - **CSRF tokens.** `CsrfViewMiddleware` compares a random secret in the cookie with a masked copy in the form; `SECRET_KEY` is not involved. - **Database-backed session identifiers.** The session key itself is a random string, not a signature; only the stored data and auth hash depend on the key. ## What happens when you change it Suppose a deploy replaces `SECRET_KEY` with a new value and leaves `SECRET_KEY_FALLBACKS` empty: 1. The next request from a logged-in user loads the session. On the `db` or `signed_cookies` backend the signature fails and the session comes back empty. 2. Even where the data survives, `get_user()` recomputes the session auth hash with the new key, finds a mismatch, calls `flush()`, and returns `AnonymousUser`. 3. Every user is therefore **logged out** at once. 4. Reset links sent before the deploy fail validation, and so do any links your app built with `signing.dumps()`, such as unsubscribe or email-confirmation links. 5. Nothing happens to passwords: users log in again with the same credentials. That outage is why Django 4.1 added **`SECRET_KEY_FALLBACKS`**: move the old key there and signatures made with it still verify, while everything new is signed with the current key. ## Practical consequences - Treat a key change as a **user-visible event**, not a config tweak. - Never let two app servers run with different keys: users bounce between them and get logged out at random. - A test or staging environment that shares production's key can forge production's signed data, so give each environment its own. - An empty value is refused: accessing `settings.SECRET_KEY` raises `ImproperlyConfigured("The SECRET_KEY setting must not be empty.")`.

  • Why are users logged out even on the cache session backend, whose data is not signed?
    Login stores the session auth hash, an HMAC of the user's password field keyed by `SECRET_KEY`. On every request `get_user()` recomputes it; with a new key it no longer matches, so Django flushes the session and returns `AnonymousUser`, unless an entry in `SECRET_KEY_FALLBACKS` still matches.
  • Does changing a user's password also log them out, and why is that related?
    Yes, on other devices. The session auth hash is an HMAC of the password field, so a new password hash produces a different auth hash and older sessions fail verification. Django's password-change view calls `update_session_auth_hash()` to keep the current session alive. The key and the password are both inputs to the same HMAC.

saying these in an interview costs you the question

  • SECRET_KEY encrypts session data so users cannot read it
  • Changing SECRET_KEY forces every user to reset their password
  • SECRET_KEY is used to salt password hashes
  • Only the signed_cookies session backend depends on SECRET_KEY
  • CSRF tokens are HMACs of the SECRET_KEY
open as a page

How should a Django project load SECRET_KEY in production, and how do you generate a strong new value for it?

level: juniorimportance: should knowfreq 45%

basics

~10 s

Read SECRET_KEY from the environment or a secrets file with no hard-coded default, so a missing value fails loudly. Generate one with django.core.management.utils.get_random_secret_key(), which returns 50 random characters; never ship startproject's 'django-insecure-' key.

open as a page

Your Django SECRET_KEY was committed to a public repository; how do you rotate it with SECRET_KEY_FALLBACKS without logging everyone out, and what does the fallback cost you?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Set a freshly generated SECRET_KEY and move the leaked key into SECRET_KEY_FALLBACKS briefly, so active sessions verify and get re-signed, then remove it. While it stays there, anything the attacker signs with it also verifies.

open as a page