Skip to content

feat(session): add login() that attaches the user through the middleware - #351

Merged
allen0099 merged 2 commits into
masterfrom
feat/293-session-login
Sep 29, 2026
Merged

allen0099 merged 2 commits into
masterfrom
feat/293-session-login

Conversation

@allen0099

@allen0099 allen0099 commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

What

Adds login(request, user) to fastapi_cachex.session: the supported way to log a user in under FastAPICacheXSessionMiddleware so that require_user_session / AuthenticatedSession accept the next request.

  • Anonymous loaded session (e.g. a cart): the user is attached, then the ID is rotated against session fixation. The data is kept and the old token no longer resolves.
  • Same user_id (a re-login): the same, and the new SessionUser replaces the stored one, so changed roles or metadata take effect.
  • Another user's session: it is deleted and request.session is emptied, so neither its data nor writes made earlier in the request reach the new user (as Django's login() flushes). A new session is started, its old token no longer resolves, and writes after login() are saved as usual.
  • No session loaded (a new visitor, or a token that did not resolve): a new session is created with the user, with the IP / User-Agent bindings the middleware would apply.
  • The middleware then sends the token through the request's transport: the response header for a header or Authorization: Bearer token, otherwise an HttpOnly Set-Cookie. The response gets Cache-Control: private, no-store, as every token-carrying response already does. Keys written to request.session before or after the call are saved into the logged-in session (earlier writes are dropped when the session was another user's).
  • login() returns the session, and get_session returns it for the rest of the request.

Edge cases (documented in the docstring and the guide)

  • Outside FastAPICacheXSessionMiddleware (no middleware, or the deprecated header-only SessionMiddleware, which cannot send a token for a session it did not load): RuntimeError with a message that names the middleware.
  • clear() after login() in the same request is a logout: the new session is deleted and no token is sent (a cookie client gets its cookie expired).
  • clear() before login(): the loaded session is logged out (deleted), and login() starts a new session instead of rotating the cleared one.
  • A request with no token gets the cookie only, consistent with examples/session_login.py: the new-visitor login sends its session cookie without Cache-Control: private, no-store #322. An API client that needs the token in the body can return manager.issue_token(session) for the returned session.
  • Calling login() twice rotates again and keeps the last user.

How

_RequestSession now records the backend session it belongs to and the middleware that created it. login() updates that record, and the middleware reads it when the response starts, instead of the session it loaded. The existing "session ID changed, so send a token for the new ID" path then handles both transports. The FastAPICacheXSessionMiddleware public API is unchanged.

Docs and example

  • The rotate_session_id docstring and SESSION.md (EN + zh-TW) now document login() as the way to log in. They state plainly that request.session["user_id"] is application data, not a login. rotate_session_id stays documented for rotating without a login.
  • examples/session_login.py uses login(). It no longer builds the cookie by hand, calls update_session() or sets Cache-Control itself. test_session_login_response_is_private still passes, since the middleware sets the header.
  • Changelog fragment changelog.d/293.added.md.

Tests

tests/session/test_login.py:

  • new visitor → login → AuthenticatedSession route, for cookie, header and Bearer;
  • switching users (cookie and header): the old token no longer resolves and the new session holds none of the previous user's data; a re-login with the same user_id keeps the data and updates the user; an anonymous login keeps writes made before login();
  • an anonymous session keeps its data under a new ID, and the old token is rejected (each transport);
  • writes before and after login(), login() then clear() (cookie and header), clear() then login(), login twice, IP / User-Agent binding;
  • RuntimeError outside the middleware and under the deprecated SessionMiddleware.

The key tests were mutation-checked: each targeted mutation of the implementation made at least one of them fail.

Closes #293

login(request, user) rotates the loaded session's ID (or starts a new session), attaches the SessionUser, and lets FastAPICacheXSessionMiddleware send the token through the request's transport, so AuthenticatedSession accepts the next request. The login example drops its hand-built cookie and update_session() call, and the session guide documents login() as the way to log in.
When the loaded session belongs to a different user_id, login() now deletes it and empties request.session instead of rotating it, so none of the previous user's data reaches the new one. Anonymous sessions and re-logins of the same user_id still keep their data and rotate.
@allen0099
allen0099 merged commit 214e9ee into master Sep 29, 2026
12 checks passed
@allen0099
allen0099 deleted the feat/293-session-login branch September 29, 2026 06:32
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.

Session: a supported login() that attaches the user through the middleware

1 participant