Using Passport.js session:false for Stateless API Routes
Use passport.authenticate with session:false to authenticate API requests without server-side sessions, and handle token persistence yourself.
15 Nov 2025, 13:23 UTC

The problem: Passport forgets the user between API requests
You add passport.authenticate('local') to an Express login route. The request returns success, but the mobile app sends the next request without credentials and req.user is undefined. Passport is not broken; it is behaving as designed. By default, Passport ties authentication to a server-side session. APIs that do not use cookies need a different setting.
What session: false changes
passport.authenticate(strategy, options) is Express middleware. When you pass { session: false }, Passport runs the strategy's verify callback for the current request, populates req.user if verification succeeds, and then stops. It does not call serializeUser to store the user in a session, and on later requests it does not call deserializeUser to restore req.user automatically.
This setting only affects Passport's own session handling. If your Express app also uses express-session for other routes, that middleware can still set cookies. Disabling Passport sessions does not make your entire application cookie-free.
Worked example: stateless login route
Add this to your Express application file, such as app.js. No special filesystem permissions are required beyond editing the file and running node app.js.
const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local');
const app = express();
app.use(express.json());
passport.use(new LocalStrategy(
{ usernameField: 'email', passwordField: 'password' },
async (email, password, done) => {
// Replace with your own user lookup and password comparison.
const user = await findUserByEmail(email);
if (!user || !(await comparePassword(password, user.passwordHash))) {
return done(null, false);
}
return done(null, user);
}
));
app.post(
'/login',
passport.authenticate('local', { session: false }),
(req, res) => {
// req.user exists only for this request.
res.json({ user: { id: req.user.id, email: req.user.email } });
}
);
After a successful POST /login, the response should contain the user object. Inspect the response headers: Passport itself should not set a session cookie. If you send a second request to a protected route without credentials or your own token, req.user should be undefined unless you have added separate token verification middleware.
Risk: browser-based flows that expect a cookie after login will not receive one from Passport. Because deserializeUser is skipped, your verify callback must return a fully populated user object on every authenticated request.
Session on versus session off
| Behavior | session: true (default) | session: false |
|---|---|---|
| Creates or restores login session | Yes | No |
Calls serializeUser | Yes | No |
Calls deserializeUser on later requests | Yes | No |
req.user persists across requests | Yes, via session | No, current request only |
| Typical client | Browser with cookies | API, mobile, or token-based client |
How to verify the result
- Open
package.jsonand note the installedpassportandpassport-localversions; behavior can differ between major versions. - Start the app locally and send credentials:
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"email":"[contact removed]","password":"example-password"}'
- Confirm the response body includes user data and that
req.userwas available inside the route handler. - Check the response headers for
Set-Cookie. Passport withsession: falsedoes not issue its own session cookie, thoughexpress-sessionmay still set one if it is mounted. - Call a protected endpoint without credentials.
req.usershould be undefined unless your application populates it through another mechanism.
Limitation to plan around
Passport does not issue JSON Web Tokens, manage token expiration, or rotate credentials. session: false makes the authentication stateless at the Passport layer, but your application still needs a token strategy—such as a JWT in an Authorization header—if clients must remain authenticated across requests without resending passwords.
Closing decision
Use session: false for API endpoints consumed by mobile clients, single-page applications with their own token storage, or service-to-service calls. Keep the default session behavior for server-rendered pages that rely on a cookie after login. In either case, verify the installed Passport major version and test that req.user appears only where you expect it.