Passport.js session serialization: configuring serializeUser to store minimal user identifier
0 reputation · 26 Mar 2020, 02:50 UTC
0 reputation · 26 Mar 2020, 02:50 UTC
Goal: Configure Passport.js's serializeUser callback so that the session store holds only the essential identifier needed to reconstruct the user object, thereby reducing memory usage and I/O latency in high‑traffic Node.js applications.
Uncertainty: It is unclear what minimal payload serializeUser should return (e.g., just the user ID, a signed token, or a compact subset of properties) and how deserializeUser must interpret that payload to fully populate req.user without breaking existing strategies that expect additional fields. Additionally, there is a need to verify whether custom strategies that attach extra properties to req.user during authentication will still function when the session contains only the identifier.
Questions: What should serializeUser return to achieve minimal session size while preserving authentication state? How should deserializeUser reconstruct req.user from that return value? Will custom strategies that rely on req.user properties set during authentication continue to work correctly?
35275 reputation · 26 Mar 2020, 09:48 UTC
serializeUser should return only the stable primary key that uniquely identifies the user in your persistence layer—typically user.id, user._id, or a UUID. deserializeUser receives that single value, performs a lookup, and passes the fully populated user object to done(null, user). Custom strategies that attach additional properties to req.user during the initial authentication will not have those properties automatically restored on subsequent requests; deserializeUser must explicitly re-attach any fields your application expects beyond the database record.
Passport’s session middleware stores whatever serializeUser returns as the session payload. Returning the primary key is the documented pattern for minimizing session size. The authentication strategy runs only during the initial login; after that, passport.session() invokes deserializeUser on every request to hydrate req.user. This behavior is orthogonal to the strategy used.
Whether custom strategies continue to work depends on whether downstream code expects properties that were attached during authentication but are not part of the standard user record. For example, an OAuth strategy might attach accessToken or refreshToken to req.user at login time. If route handlers or middleware read those fields on later requests, they will be undefined unless deserializeUser re-populates them from a separate store or a database column.
serializeUser to return the identifier:passport.serializeUser((user, done) => {
done(null, user.id);
});deserializeUser to fetch the full record and re-attach any non-database fields your application requires:passport.deserializeUser((id, done) => {
User.findById(id)
.then(user => {
if (!user) return done(null, false);
// Re-attach fields expected by custom strategies or middleware
user.permissions = loadPermissionsFor(id);
done(null, user);
})
.catch(err => done(err));
});deserializeUser is registered before any middleware or route that accesses req.user.After login, inspect the session store directly. In Redis, the session key should contain only the identifier rather than a serialized user object:
redis-cli GET "session:<session-id>"Add temporary logging inside deserializeUser to confirm it receives the identifier and returns the expected shape. Finally, exercise a protected route and assert that every property consumed by your controllers exists on req.user.
Do any custom strategies or downstream middleware depend on req.user properties that are attached only during the initial authentication and are not stored in your user table? If yes, you must either persist those fields in a separate server-side store keyed by the user ID or re-fetch them in deserializeUser. If no, the identifier-only pattern is sufficient.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.