settings: Refresh the session cookie on every request #3

Merged
exler merged 1 commit from fix/session-sliding-expiry into main 2026-10-10 16:08:17 +02:00
Collaborator

Summary

Hearth configured nothing about sessions, so Django defaults applied and the session cookie age was fixed at login time. An active user was still logged out 14 days after logging in, because nothing ever re-saved the session. SESSION_SAVE_EVERY_REQUEST = True makes SESSION_COOKIE_AGE a sliding window that refreshes on use.

Root cause

  • hearth/settings.py set no SESSION_ENGINE, SESSION_COOKIE_AGE, or SESSION_SAVE_EVERY_REQUEST, so Django's defaults applied (backends.db, 1209600, False).
  • django.contrib.auth.login() never calls set_expiry(), so SessionBase.get_expiry_age() falls back to the constant get_session_cookie_age() = 1209600 s. The expiry is anchored to the moment the cookie was issued and never moves with activity.
  • With SESSION_SAVE_EVERY_REQUEST = False, SessionMiddleware.process_response() re-issues the cookie only when request.session.modified is true. Ordinary browsing does not modify the session, so the window never slid.

Nothing else was clearing sessions: docker/entrypoint.sh only runs migrate and starts Daphne, seed_hearth deletes only recipe ingredients, and hearth_data persists across the container stops performed by docker-volume-backup.

Change

hearth/settings.py — one setting plus a comment:

# Sessions
# SESSION_COOKIE_AGE is fixed at login unless the session is re-saved, so an
# active user still gets logged out 14 days after logging in. Saving on every
# request turns the cookie age into a sliding window that refreshes on use.
SESSION_SAVE_EVERY_REQUEST = True

Verification

python manage.py check:

System check identified no issues (0 silenced).

python manage.py test core:

Ran 8 tests in 2.997s

OK

ruff check hearth/settings.py and ruff format --check hearth/settings.py: all checks passed, already formatted. git diff --check: clean.

Behavioural check on a throwaway test database (log in, then issue one plain authenticated GET that writes nothing, and compare both setting values):

SESSION_SAVE_EVERY_REQUEST=True
  login status            : 302
  sessionid re-issued     : yes (Max-Age=1209600, expires=Sat, 24 Oct 2026 14:05:54 GMT)
  DB expire_date advanced : True
    2026-10-24T14:05:54.661037+00:00 -> 2026-10-24T14:05:54.675075+00:00
SESSION_SAVE_EVERY_REQUEST=False
  login status            : 302
  sessionid re-issued     : no
  DB expire_date advanced : False
    2026-10-24T14:05:55.025176+00:00 -> 2026-10-24T14:05:55.025176+00:00

So with the flag on, each request re-issues the session cookie and pushes the DB expiry out by another SESSION_COOKIE_AGE; with the flag off (current production behaviour) both stay frozen at login.

Risk and rollback

Every request now writes one django_session row (SQLite, WAL, busy_timeout=5000). At this scale the extra write is negligible; cached_db is available later if it ever matters. Rollback is deleting the one line.

Not covered

Sessions are still only 14 days from the last request, and hearth.marut.network and the LAN IP 192.168.50.200 remain separate cookie jars, so switching hosts still requires a fresh login. Raising SESSION_COOKIE_AGE or adding SESSION_COOKIE_SECURE/__Host- hardening would be a follow-up.

## Summary Hearth configured nothing about sessions, so Django defaults applied and the session cookie age was fixed at login time. An active user was still logged out 14 days after logging in, because nothing ever re-saved the session. `SESSION_SAVE_EVERY_REQUEST = True` makes `SESSION_COOKIE_AGE` a sliding window that refreshes on use. ## Root cause - `hearth/settings.py` set no `SESSION_ENGINE`, `SESSION_COOKIE_AGE`, or `SESSION_SAVE_EVERY_REQUEST`, so Django's defaults applied (`backends.db`, `1209600`, `False`). - `django.contrib.auth.login()` never calls `set_expiry()`, so `SessionBase.get_expiry_age()` falls back to the constant `get_session_cookie_age()` = 1209600 s. The expiry is anchored to the moment the cookie was issued and never moves with activity. - With `SESSION_SAVE_EVERY_REQUEST = False`, `SessionMiddleware.process_response()` re-issues the cookie only when `request.session.modified` is true. Ordinary browsing does not modify the session, so the window never slid. Nothing else was clearing sessions: `docker/entrypoint.sh` only runs `migrate` and starts Daphne, `seed_hearth` deletes only recipe ingredients, and `hearth_data` persists across the container stops performed by `docker-volume-backup`. ## Change `hearth/settings.py` — one setting plus a comment: ```python # Sessions # SESSION_COOKIE_AGE is fixed at login unless the session is re-saved, so an # active user still gets logged out 14 days after logging in. Saving on every # request turns the cookie age into a sliding window that refreshes on use. SESSION_SAVE_EVERY_REQUEST = True ``` ## Verification `python manage.py check`: ``` System check identified no issues (0 silenced). ``` `python manage.py test core`: ``` Ran 8 tests in 2.997s OK ``` `ruff check hearth/settings.py` and `ruff format --check hearth/settings.py`: all checks passed, already formatted. `git diff --check`: clean. Behavioural check on a throwaway test database (log in, then issue one plain authenticated GET that writes nothing, and compare both setting values): ``` SESSION_SAVE_EVERY_REQUEST=True login status : 302 sessionid re-issued : yes (Max-Age=1209600, expires=Sat, 24 Oct 2026 14:05:54 GMT) DB expire_date advanced : True 2026-10-24T14:05:54.661037+00:00 -> 2026-10-24T14:05:54.675075+00:00 SESSION_SAVE_EVERY_REQUEST=False login status : 302 sessionid re-issued : no DB expire_date advanced : False 2026-10-24T14:05:55.025176+00:00 -> 2026-10-24T14:05:55.025176+00:00 ``` So with the flag on, each request re-issues the session cookie and pushes the DB expiry out by another `SESSION_COOKIE_AGE`; with the flag off (current production behaviour) both stay frozen at login. ## Risk and rollback Every request now writes one `django_session` row (SQLite, WAL, `busy_timeout=5000`). At this scale the extra write is negligible; `cached_db` is available later if it ever matters. Rollback is deleting the one line. ## Not covered Sessions are still only 14 days from the last request, and `hearth.marut.network` and the LAN IP `192.168.50.200` remain separate cookie jars, so switching hosts still requires a fresh login. Raising `SESSION_COOKIE_AGE` or adding `SESSION_COOKIE_SECURE`/`__Host-` hardening would be a follow-up.
settings: Refresh the session cookie on every request
All checks were successful
Push Docker Image / push (push) Successful in -40s
944179e5bd
SESSION_COOKIE_AGE is applied at login time and only re-applied when the
session is saved, so an active user was still logged out 14 days after
logging in. Saving the session on every request makes the cookie age a
sliding window that refreshes on use.
exler merged commit 944179e5bd into main 2026-10-10 16:08:17 +02:00
exler deleted branch fix/session-sliding-expiry 2026-10-10 16:08:17 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
exler/hearth!3
No description provided.