Passport.js session serialization vs passport-jwt with session:false when the load balancer already pins sticky sessions
0 reputation · 13 Oct 2023, 22:46 UTC
0 reputation · 13 Oct 2023, 22:46 UTC
I'm designing authentication for an Express app that will run behind a load balancer configured with sticky sessions (cookie-based affinity). Passport supports both approaches I'm weighing: the classic serializeUser/deserializeUser hooks backed by express-session, and the passport-jwt strategy with session: false so no server-side state is kept at all.
My concrete constraint is revocation. With sessions, logging out or disabling an account is a store delete. With stateless JWTs, the token stays valid until exp, so I'd need short-lived access tokens plus refresh rotation, or a denylist keyed by jti — which reintroduces the shared state I was trying to avoid. Sticky sessions make even the default session setup work today, but I don't want to depend on affinity if the fleet later scales or rebalances.
Assume Express 4, current Passport 0.6/0.7 line, and HTTPS everywhere.
Given that revocation matters and sticky sessions are guaranteed only for now, which trade-off is more defensible: a Redis-backed session store from day one, or JWT with short exp and refresh rotation? Does skipping deserializeUser per request with session: false meaningfully reduce latency if user lookup hits a database? Are there pitfalls running both strategies side by side (sessions for browser routes, JWT for API routes) in one Passport setup?
Use a Redis-backed session store from day one. Given your stated constraints—revocation matters and sticky affinity is guaranteed only for now—the classic serializeUser/deserializeUser model with a shared session store is the more defensible trade-off. passport-jwt with session: false is a good fit when you can tolerate a revocation window or when you already have shared state for refresh-token rotation or token-version checks. It does not remove shared state if revocation is hard.
Confirmed behavior: with session-backed Passport, serializeUser stores an identifier in the session and deserializeUser turns it back into req.user on each request. With passport-jwt and session: false, Passport does not create or read a session, so deserializeUser is not part of the request path. Confirmed: stateless JWTs cannot be revoked before exp without some shared check. Likely for your app: the operational simplicity of deleting a Redis session on logout or account disable will outweigh the theoretical statelessness benefit.
Short access-token exp plus refresh rotation limits the revocation window but does not eliminate it. To disable an account immediately, you still need a denylist keyed by jti, a per-user token version, or a refresh-token store. That is shared state. If you store JWTs in browser cookies, you also reintroduce CSRF concerns; if you store them in browser storage, you accept XSS exfiltration risk.
session: false help?Only if your deserializeUser performs a database lookup on every request. If it does, skipping it is a real saving. But a Redis session lookup is also one network round trip, and JWT verification is local CPU work. If you add a revocation check for JWT, the gap shrinks or disappears. Measure with your real session payload and user-fetch cost rather than assuming JWT is faster.
It is supported, but keep the middleware scoped. Register both strategies, then choose per route:
app.use(passport.initialize());
// Browser routes
browserRouter.use(passport.session());
// API routes
apiRouter.get('/me',
passport.authenticate('jwt', { session: false }),
handler
);Pitfalls: calling passport.session() globally and then req.login() on API requests can silently create sessions. Session users and JWT claims may have different shapes, so normalize req.user. CSRF protection belongs on cookie-session routes; bearer-token routes generally do not need it, but cookie-stored JWTs do. Logout must clear the session and any refresh cookie.
exp or refresh denial.req.session after API calls to confirm session: false routes never create session cookies.Version assumption: Express 4 and the Passport 0.6/0.7 line. Test logout and session regeneration on your exact patch before relying on them.
Does your current deserializeUser query the database on every request, or does the session already carry enough user data? If it queries every time, session: false may save a hop. If it does not, the latency argument for JWT is weak.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.