Passport 0.6 deserializeUser error handling divergence between done(err) and done(null, false)
0 reputation · 21 Nov 2022, 17:48 UTC
0 reputation · 21 Nov 2022, 17:48 UTC
This question addresses how Passport 0.6's deserializeUser callback should distinguish between a missing user record and an operational failure when reconstructing req.user, given the framework's changed error propagation model. The 0.6 release shifted authenticate middleware error handling from throwing to next(err), which alters how strategy failures propagate through Express error middleware. Concurrently, deserializeUser may call done(err), done(null, false), or done(null, undefined), each producing distinct req.user values and downstream authorization behavior. No built-in schema or session versioning exists, so the chosen pattern directly impacts session durability across deployments.
In Passport 0.6 the deserializeUser callback must be used to distinguish two fundamentally different situations:
done(err) so the error propagates via next(err) to Express’s error handler.done(null, false) so req.user becomes false and the request is treated as unauthenticated.Calling done(null, undefined) behaves like done(null, false) in 0.6, but explicit false is clearer for downstream checks.
authenticate’s next(err)Prior to 0.6, strategy errors inside deserializeUser were thrown and caught by local try/catch wrappers around authenticate. 0.6 replaces that with next(err), so:
err passed to done is forwarded to Express’s error middleware. Your global error handler can then decide whether to return a 500 or a custom status.null, false result bypasses error middleware; req.user becomes false and any route that requires authentication will typically respond with 401 or 403.Because Passport stores only the serialized user ID, a change in deserializeUser can silently alter req.user for all existing sessions. The safest pattern is:
sessionVersion field on the user model (or a dedicated session store column).deserializeUser implementation.done(null, false) to force re‑authentication.If you cannot add a version field, the fallback is to invalidate all sessions by rotating the session secret or clearing the session store entirely. This guarantees that no session will be misinterpreted under the new deserializeUser contract.
To ensure the chosen pattern works, run a quick test:
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
if (err) return done(err); // triggers next(err)
if (!user) return done(null, false); // req.user === false
done(null, user);
});
});
After deployment, monitor the Express error middleware for unexpected 500 responses and confirm that unauthenticated routes receive 401/403 when req.user is false.
Do you currently store a sessionVersion or equivalent version marker in your session or user records? Knowing this will help refine the migration strategy.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.