Passport.js 0.6.0 req.logout() asynchronous transition
0 reputation · 07 Sept 2026, 11:48 UTC
0 reputation · 07 Sept 2026, 11:48 UTC
Updating a Node.js application to Passport.js version 0.6.0 introduces a breaking change to the req.logout() method. In previous versions, this method operated synchronously, allowing developers to call it without a callback function before redirecting the user.
Starting with version 0.6.0, req.logout() is asynchronous and requires a callback function to handle the completion of the session termination. When migrating legacy codebases, calling this method synchronously may lead to unexpected behavior or failure to clear the session before the response is sent to the client.
The transition requires a shift in how the authentication middleware handles the request-response cycle during logout. Specifically, the application must ensure the session is fully cleared before executing the final redirect or response.
30275 reputation · 07 Sept 2026, 17:31 UTC
In Passport.js 0.6.0, req.logout() became asynchronous and requires a callback. The fix has two parts: move your redirect (or any response) inside that callback, and forward any error the callback receives to Express via next(err). The minimal working pattern is:
app.post('/logout', (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
res.redirect('/');
});
});That is the entire migration for most codebases. The details below explain why and cover the edge cases.
Before 0.6.0, this was safe:
// Passport < 0.6.0 — synchronous, no longer correct
app.post('/logout', (req, res) => {
req.logout();
res.redirect('/');
});Under 0.6.0, req.logout() returns immediately and finishes clearing the session (including any session-store writes) later. Because res.redirect() runs first, the response can reach the client before the session is actually invalidated. The classic symptom: the user is redirected to the login page, clicks "back" or refreshes a protected route, and is still authenticated. This is a race condition, so it may appear intermittent — especially with a remote session store like Redis, where the write latency is higher than with the default in-memory store.
The callback receives one argument: an error, or nothing on success. The rule is simple — anything that must happen after the session is cleared goes inside the callback:
app.post('/logout', (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
// Session is invalidated at this point.
// Safe to also destroy the session cookie entirely:
req.session.destroy((destroyErr) => {
if (destroyErr) return next(destroyErr);
res.clearCookie('connect.sid');
res.redirect('/login');
});
});
});Note the distinction: req.logout() removes the passport property from the session; req.session.destroy() removes the whole session record. If your logout intent is "fully forget this browser," do both, as above. If you store other data in the session that should survive logout (e.g., a cart), call only req.logout().
Passing err to next() routes failures into your existing Express error-handling middleware, which keeps logout consistent with the rest of the app. If you prefer async/await style, wrap the call in a Promise — but do not forget that Express 4 does not catch rejected promises automatically, so use .catch(next):
app.post('/logout', (req, res, next) => {
new Promise((resolve, reject) => {
req.logout((err) => (err ? reject(err) : resolve()));
})
.then(() => res.redirect('/'))
.catch(next);
});npm ls passport — the callback requirement applies to 0.6.0 and later.KEYS * in Redis) to confirm the session record is gone before the redirect completes.One caveat: if you have multiple logout entry points (AJAX endpoints, token revocation routes), each one needs the same callback treatment — a single missed call site reintroduces the race.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.