Why is PassportJS in Node not removing session on logout
Solution 1
Brice’s answer is great, but I still noticed an important distinction to make; the Passport guide suggests using .logout()
(also aliased as .logOut()
) as such:
app.get('/logout', function(req, res){
req.logout();
res.redirect('/'); //Can fire before session is destroyed?
});
But as mentioned above, this is unreliable. I found it behaved as expected when implementing Brice’s suggestion like this:
app.get('/logout', function (req, res){
req.session.destroy(function (err) {
res.redirect('/'); //Inside a callback… bulletproof!
});
});
Hope this helps!
Solution 2
Ran into the same issue. Using req.session.destroy();
instead of req.logout();
works, but I don't know if this is the best practice.
Solution 3
session.destroy
may be insufficient, to make sure the user is fully logged out you have to clear session cookie as well.
The issue here is that if your application is also used as an API for a single page app (not recommended but quite common) then there can be some request(s) being processed by express that started before logout and end after logout. If this were the case then this longer running request will restore the session in redis after it was deleted. And because the browser still has the same cookie the next time you open the page you will be successfully logged in.
req.session.destroy(function() {
res.clearCookie('connect.sid');
res.redirect('/');
});
That's the what maybe happening otherwise:
- Req 1 (any request) is received
- Req 1 loads session from redis to memory
- Logout req received
- Logout req loads session
- Logout req destroys session
- Logout req sends redirect to the browser (cookie is not removed)
- Req 1 completes processing
- Req 1 saves the session from memory to redis
- User opens the page without login dialog because both the cookie and the session are in place
Ideally you need to use token authentication for api calls and only use sessions in web app that only loads pages, but even if your web app is only used to obtain api tokens this race condition is still possible.
Solution 4
I was having the same issue, and it turned out to not be a problem with Passport functions at all, but rather in the way I was calling my /logout
route. I used fetch to call the route:
(Bad)
fetch('/auth/logout')
.then([other stuff]);
Turns out doing that doesn't send cookies so the session isn't continued and I guess the res.logout()
gets applied to a different session? At any rate, doing the following fixes it right up:
(Good)
fetch('/auth/logout', { credentials: 'same-origin' })
.then([other stuff]);
Solution 5
I was having the same issues, capital O fixed it;
app.get('/logout', function (req, res){
req.logOut() // <-- not req.logout();
res.redirect('/')
});
Edit: this is no longer an issue.
Jeffrey Chen
Updated on September 07, 2021Comments
-
Jeffrey Chen over 2 years
I am having trouble getting my system to log out with PassportJS. It seems the logout route is being called, but its not removing the session. I want it to return 401, if the user is not logged in in specific route. I call authenticateUser to check if user is logged in.
Thanks a lot!
/******* This in index.js *********/ // setup passport for username & passport authentication adminToolsSetup.setup(passport); // admin tool login/logout logic app.post("/adminTool/login", passport.authenticate('local', { successRedirect: '/adminTool/index.html', failureRedirect: '/', failureFlash: false }) ); app.get('/adminTool/logout', adminToolsSetup.authenticateUser, function(req, res){ console.log("logging out"); console.log(res.user); req.logout(); res.redirect('/'); }); // ******* This is in adminToolSetup ******** // Setting up user authentication to be using user name and passport as authentication method, // this function will fetch the user information from the user name, and compare the password for authentication exports.setup = function(passport) { setupLocalStrategy(passport); setupSerialization(passport); } function setupLocalStrategy(passport) { passport.use(new LocalStrategy( function(username, password, done) { console.log('validating user login'); dao.retrieveAdminbyName(username, function(err, user) { if (err) { return done(err); } if (!user) { return done(null, false, { message: 'Incorrect username.' }); } // has password then compare password var hashedPassword = crypto.createHash('md5').update(password).digest("hex"); if (user.adminPassword != hashedPassword) { console.log('incorrect password'); return done(null, false, { message: 'Incorrect password.' }); } console.log('user validated'); return done(null, user); }); } )); } function setupSerialization(passport) { // serialization passport.serializeUser(function(user, done) { console.log("serialize user"); done(null, user.adminId); }); // de-serialization passport.deserializeUser(function(id, done) { dao.retrieveUserById(id, function(err, user) { console.log("de-serialize user"); done(err, user); }); }); } // authenticating the user as needed exports.authenticateUser = function(req, res, next) { console.log(req.user); if (!req.user) { return res.send("401 unauthorized", 401); } next(); }
-
Michael over 10 yearsAdding req.session.destroy(); worked for me, I had the same problem
-
Hrushikesh over 10 yearsreq.session.destroy(); worked for me too and remember to clear you cookies with res.clearCookie('cookiename'). However does anyone knows what exactly req.logout() do?
-
ztirom over 10 years@WebHrushi you can find the function in passport/lib/passport/http/request.js. Today both versions stopped working for me, redirect seems in both ways called before the session is finally destroyed. On the redirected page both ways described here seem to be working fine.
-
Gavin almost 10 yearsI don't like the idea of not using
req.logout()
, for forward compatibility and whatnot, so I just addedreq.session.destroy();
afterreq.logout()
and that works fine. -
schlenger over 9 yearsI just get 'Object #<Session> has no method 'destroy'' as errormessage
-
jlmakes over 9 years@schlenger Hmm, the above is using Express 3. Are you using version 4?
-
cyberwombat over 9 yearsThe current version of Passport accepts either logOut or logout (same with login and logIn)
-
IIllIIll about 8 yearsI'm not using Angular so this doesn't make any sense. :/
-
Godfather almost 8 yearswhy delay of 2 seconds, when you can use an asynchronous callback
-
Matt Browne almost 8 yearsThe callback was not firing consistently for me.
-
frooble about 7 yearsBe careful because, in the example given here, the usage of session.destroy assumes use of the express-sessions module. Other session modules (e.g. mozilla node-client-sessions) adds a destroy method that returns synchronously only and disregards any callback pararmeter. In which case your code will hang
-
Tom Hughes almost 7 yearsI was having issues with both logout and login occasionally not sticking, and this fixed it for me.
-
Charlie Fish almost 7 years@TomHughes Glad it helped!!
-
Fareed Alnamrouti almost 7 yearsyour code can lead to an unexpected behavior if the cache is slow or if you have a middle ware running after the logout logic, you should use req.session.destroy(()=> res.redirect("/"));
-
Green over 6 years
session.destroy
is asynchronous and requires a callback. You should callres.redirect('/');
in that callback. -
oztune over 6 yearsAre you basing this on
client-sessions
? It's destroy function doesn't take an argument: github.com/mozilla/node-client-sessions/blob/master/lib/… -
Paul S almost 6 yearsThis solves the issue on the client, rather than on the server, which seems insecure. if
connect.sid
gets hijacked before you log out it shouldn't allow another party to login as you after you have logged out. -
Paul S almost 6 years
-
emonhossain about 4 yearsok I use your second method. The session file in the sessions folder is deleted and the session cookie is removed from the client, but the Node server shows following error message in the console:
[session-file-store] will retry, error on last attempt: Error: ENOENT: no such file or directory, open ...
how could I fix this? -
Dinesh Nadimpalli almost 4 yearsWelcome! Please refer to this guide for improving your answers stackoverflow.com/help/how-to-answer
-
Azhar Husain over 3 yearsWas searching for 4 hours before ending up on this answer, a silly mistake but thanks for pointing out.
-
Andrew Nolan over 3 yearsThanks for trying to support. I would suggest fixing the code formatting in your answer. The main snippet you posted isn't valid JS.
-
10 Rep over 3 yearsWhy will this code work? Can you provide more detail around that? Thanks!