Skip to content

Track session access and modification in SessionMiddleware#3166

Merged
Kludex merged 4 commits intomainfrom
track-session-access-modification
Mar 1, 2026
Merged

Track session access and modification in SessionMiddleware#3166
Kludex merged 4 commits intomainfrom
track-session-access-modification

Conversation

@Kludex
Copy link
Copy Markdown
Owner

@Kludex Kludex commented Mar 1, 2026

Summary

SessionMiddleware currently sends Set-Cookie on every response when the session is non-empty, even if nothing changed. This causes race conditions with concurrent requests - a slow read-only response can clobber a newer session cookie (see #2019, Chrome bug, authlib#334).

This PR replaces the plain dict used for scope["session"] with a Session subclass that tracks two flags:

  • accessed: set in HTTPConnection.session when the session is accessed, following Flask 3.1.3's approach of tracking at the property level rather than per-method overrides
  • modified: set on write operations (__setitem__, __delitem__, clear, update), with pop and setdefault conditionally marking modified only when the session actually changes (following Django and Flask/Werkzeug conventions)

The middleware then uses these flags to:

  • Only send Set-Cookie when modified is True
  • Add Vary: Cookie when accessed is True, so reverse proxies and CDNs don't cache session-dependent responses incorrectly

Kludex added 2 commits March 1, 2026 18:39
Replace the plain `dict` used for `scope["session"]` with a `Session`
subclass that tracks `accessed` and `modified` flags.

- Only send `Set-Cookie` when the session was actually modified, not on
  every response. This prevents race conditions with concurrent requests
  where a slow read-only response clobbers a newer cookie.
- Add `Vary: Cookie` header when the session is accessed, so reverse
  proxies and CDNs don't serve cached responses with session-dependent
  content to the wrong user.
- `pop` and `setdefault` conditionally set `modified` only when the
  session actually changes, following Django and Flask conventions.

Closes #2019
The dict_keys/dict_values/dict_items concrete types are not importable
in Python, so use typing.Any as the return type to satisfy mypy strict
mode without type: ignore comments.
@Kludex Kludex force-pushed the track-session-access-modification branch 4 times, most recently from 078743c to cd7d3de Compare March 1, 2026 18:12
Move access tracking from per-method overrides (keys/values/items) on
the Session dict to the HTTPConnection.session property, matching
Flask 3.1.3's approach. This avoids the dict_keys/dict_values/dict_items
typing problem entirely since those types are not importable in Python.
@Kludex Kludex force-pushed the track-session-access-modification branch from cd7d3de to dce3495 Compare March 1, 2026 18:13
@Kludex Kludex changed the title Track session access and modification in SessionMiddleware Track session access and modification in SessionMiddleware Mar 1, 2026
@Kludex Kludex merged commit 4647e53 into main Mar 1, 2026
6 checks passed
@Kludex Kludex deleted the track-session-access-modification branch March 1, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SessionMiddleware sends a new set-cookie for every request, with unintended results

1 participant