Passport.js session management requiring manual session regeneration
0 reputation · 09 Mar 2022, 11:17 UTC
0 reputation · 09 Mar 2022, 11:17 UTC
Passport.js handles user session serialization and deserialization, but it does not automatically rotate the session ID upon a successful authentication. While the framework manages the user object within the session store, the underlying session identifier remains constant unless the developer explicitly intervenes.
To mitigate session fixation risks, current implementations require manually invoking req.session.regenerate() within the callback. This creates a challenge when the authentication strategy involves side-effects, such as user record creation or profile updates, where network retries or rapid repeated requests might lead to duplicate writes if not handled idempotently.
35275 reputation · 09 Mar 2022, 21:30 UTC
No built-in automatic session rotation exists in Passport.js. The framework delegates session lifecycle entirely to the underlying session middleware (typically express-session). You must call req.session.regenerate() explicitly.
For idempotency during database writes before regeneration: use a database-level upsert (insert or update) on a unique constraint (e.g., provider ID + provider name, or email) rather than a plain insert. Pair this with a short-lived \"pending authentication\" row or a distributed lock if your strategy creates auxiliary records (profiles, tokens, audit logs) that must not be duplicated under retries.
verify callback.WHERE provider_id = ? AND provider = ?). Return the stable internal user ID.req.session.regenerate() after the upsert succeeds but before req.login(user) (or passport.authenticate callback returns).regenerate callback.req.login(user) to attach the user to the fresh session.passport.use(new OAuth2Strategy(..., async (accessToken, refreshToken, profile, done) => {\n try {\n // 1. Idempotent upsert\n const user = await db.users.upsert({\n where: { providerId: profile.id, provider: 'github' },\n create: { providerId: profile.id, provider: 'github', email: profile.emails[0].value },\n update: { lastLogin: new Date() }\n });\n\n // 2. Regenerate session ID\n req.session.regenerate((err) => {\n if (err) return done(err);\n // 3. Migrate needed data (example: OAuth state)\n req.session.oauthState = req.session.oauthState; // or pull from a temp store\n // 4. Log in on the new session\n req.login(user, (loginErr) => done(loginErr, user));\n });\n } catch (e) { done(e); }\n}));\n\n\nPassport's serializeUser/deserializeUser only control what is stored in the session, not the session identifier itself. express-session keeps the same SID unless regenerate() is called. This design keeps Passport strategy-agnostic but places the security responsibility on the application.
regenerate() wipes the existing session store entry; any data written before regeneration must be copied manually.req.login() before regenerate() logs the user into the old session, defeating fixation protection.connect.sid) in devtools before and after a successful login; it must change.ab -n 20 -c 5) and confirm only one user row exists.Which session store are you using (MemoryStore, connect-redis, express-session with a custom store)? The regeneration behavior is consistent, but store-specific latency or atomicity guarantees can affect the exact retry/lock strategy you should adopt.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.