Why is PassportJS in Node not removing session on logout - javascript

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();
}

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!

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.

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.

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]);

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.

I used both req.logout() and req.session.destroy() and works fine.
server.get('/logout', (req, res) => {
req.logout();
req.session.destroy(()=>{
res.redirect('/');
});
});
Just to mention, i use Redis as session store.

I was recently having this same issue and none of the answers fixed the issue for me. Could be wrong but it does seem to have to do with a race condition.
Changing the session details to the options below seems to have fixed the issue for me. I have tested it about 10 times or so now and everything seems to be working correctly.
app.use(session({
secret: 'secret',
saveUninitialized: false,
resave: false
}));
Basically I just changed saveUninitialized and resave from true to false. That seems to have fixed the issue.
Just for reference I'm using the standard req.logout(); method in my logout path. I'm not using the session destroy like other people have mentioned.
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});

None of the answers worked for me so I will share mine
app.use(session({
secret: 'some_secret',
resave: false,
saveUninitialized: false,
cookie: {maxAge: 1000} // this is the key
}))
and
router.get('/logout', (req, res, next) => {
req.logOut()
req.redirect('/')
})

Destroying session by yourself looks weird.
I faced with this issue having next configuration:
"express": "^4.12.3",
"passport": "^0.2.1",
"passport-local": "^1.0.0",
I should say that this configuration works well.
The reason of my issue was in custom sessionStore that I defined here:
app.use(expressSession({
...
store: dbSessionStore,
...
}));
To be sure that your issue here too just comment store line and run without session persisting. If it will work you should dig into your custom session store. In my case set method was defined wrong. When you use req.logout() session store destroy() method not invoked as I thought before. Instead invoked set method with updated session.
Good luck, I hope this answer will help you.

I got an experience that, sometime it's doesn't work because you fail to to setup passport properly.
For example, I do vhost, but on main app I setup passport like this which is wrong.
app.js (why wrong ? please see blockqoute below)
require('./modules/middleware.bodyparser')(app);
require('./modules/middleware.passport')(app);
require('./modules/middleware.session')(app);
require('./modules/app.config.default.js')(app, express);
// default router across domain
app.use('/login', require('./controllers/loginController'));
app.get('/logout', function (req, res) {
req.logout();
res.redirect('/');
});
// vhost setup
app.use(vhost('sub1.somehost.dev', require('./app.host.sub1.js')));
app.use(vhost('somehost.dev', require('./app.host.main.js')));
actually, it must not be able to login, but I manage to do that because, I continue to do more mistake. by putting another passport setup here, so session form app.js available to app.host.sub1.js
app.host.sub1.js
// default app configuration
require('./modules/middleware.passport')(app);
require('./modules/app.config.default.js')(app, express);
So, when I want to logout... it's not work because app.js was do something wrong by start initialize passport.js before express-session.js, which is wrong !!.
However, this code can solved the issues anyway as others mention.
app.js
app.get('/logout', function (req, res) {
req.logout();
req.session.destroy(function (err) {
if (err) {
return next(err);
}
// destroy session data
req.session = null;
// redirect to homepage
res.redirect('/');
});
});
But in my case the correct way is... swap the express-session.js before passport.js
document also mention
Note that enabling session support is entirely optional, though it is
recommended for most applications. If enabled, be sure to use
express.session() before passport.session() to ensure that the login
session is restored in the correct order.
So, resolved logout issue on my case by..
app.js
require('./modules/middleware.bodyparser')(app);
require('./modules/middleware.session')(app);
require('./modules/middleware.passport')(app);
require('./modules/app.config.default.js')(app, express);
// default router across domain
app.use('/login', require('./controllers/loginController'));
app.get('/logout', function (req, res) {
req.logout();
res.redirect('/');
});
app.host.sub1.js
// default app configuration
require('./modules/app.config.default.js')(app, express);
and now req.logout(); is work now.

Apparently there are multiple possible causes of this issue. In my case the problem was wrong order of declarations i.e. the logout endpoint was declared before passport initialization. The right order is:
app.use(passport.initialize());
app.use(passport.session());
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});

simply adding req.logOut(); solved this issue ; "O" should be capitalized

I was having the same issue. Turned out that my version of passport wasn't compatible with Express 4.0. Just need to install an older version.
npm install --save express#3.0.0

This worked for me:
app.get('/user', restrictRoute, function (req, res) {
res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate,
max-stale=0, post-check=0, pre-check=0');
});
It makes sure that your page won't get stored in cache

I'm working with a programmer, that suggests to remove user of req:
app.get('/logout', function (req, res){
req.session.destroy(function (err) {
req.user = null;
res.redirect('/'); //Inside a callback… bulletproof!
});
});
Reason:
we need to remove from req(passportjs also doing this but async way) because there is no use of user data after logout
even this will save memory and also might be passportjs found user data and may create new session and redirect(but not yet happen)
By the ways, this is our responsibility to remove irrelevant thing. PassportJS assign data into req.user after login and also remove if we use req.logout() but it may not works properly some times as NodeJS Asynchronous in nature

I faced the similar problem with Passport 0.3.2.
When I use Custom Callback for the passport login and signup the problem persisted.
The problem was solved by upgrading to Passport 0.4.0 and adding the lines
app.get('/logout', function(req, res) {
req.logOut();
res.redirect('/');
});

Since you are using passport authentication which uses it's own session via the connect.sid cookie this simplest way of dealing with logging out is letting passport handle the session.
app.get('/logout', function(req, res){
if (req.isAuthenticated()) {
req.logOut()
return res.redirect('/') // Handle valid logout
}
return res.status(401) // Handle unauthenticated response
})

All examples here do a redirect after the req.session.destroy.
But do realise that Express will create a new session instantly for the page you are redirecting to.
In combination with Postman I found the strange behaviour that doing a Passport-Login right after the logout gives the effect that Passport is successful but cannot store the user id to the session file. The reason is that Postman needs to update the cookie in all requests for this group, and this takes a while.
Also the redirect in the callback of the destroy does not help.
I solved it by not doing a redirect but just returning a json message.

This is still an issue.
What I did was to use req.session.destroy(function (err) {}); on the server side and on the client side, whenever they logout:
const logout = () => {
const url = '/users/logout'
fetch(url)
setTimeout(function () {
location.reload(); }, 500);
That way, when refreshing the page, the user is without session. Just make sure you are redirecting to the correct page if no one is authenticated.
Not the best approach, perhaps, but it works.

You can try manually regenerating the session:
app.get('/logout', (req, res) => {
req.logOut();
req.session.regenerate(err => {
err && console.log(err);
});
res.redirect('/');
});
This does not remove other data (like passport) from the session.

Try this
app.get('/logout', (req, res) => {
req.logout();
req.session.destroy();
res.redirect('/');
}

I solved this problem by setting the withCredentials: true on my axios.post request to the logout route. I guess the required credentials to identify the session weren't being sent over so the req.logOut() had no effect (I also noticed that req.user was undefined on the log out route, which was a big clue)

I managed to resolve a similar problem by changing the code in my client where I made the request, replacing the following:
const res = await axios.get("http://localhost:4000/auth/logout");
with this:
window.open("http://localhost:4000/auth/logout", "_self");

For me req.logout worked but I don't why req.logout() not working. How function call is not working

I do this
window.open(http://localhost:4000/auth/logout, "_self");
in the function before window.open call to e.preventDefault()
this is recommended because when you do click in log out Button you refresh the page, and the function isn't call it
function logout(e) {
e.preventDefault()
window.open(http://localhost:4000/auth/logout, "_self");
}
3 January 2022

You shoulde be using req.logout() to destroy the session in the browser.
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/'); // whatever the route to your default page is
});

I don't know how but ng-href="/signout" solved my problem. Previously I have used service to logout, but instead I've used it directly.

In my case, using a callback passed to req.session.destroy helped only some of the time and I had to resort to this hack:
req.session.destroy();
setTimeout(function() {
res.redirect "/";
}, 2000);
I don't know why that's the only solution that I've been able to get to work, but unfortunately #JulianLloyd's answer did not work for me consistently.
It may have something to do with the fact that my live login page uses SSL (I haven't been able to reproduce the issue on the staging site or my localhost). There may be something else going on in my app too; I'm using the derby-passport module since my app is using the Derby framework, so it's difficult to isolate the problem.
It's clearly a timing issue because I first tried a timeout of 100 ms, which wasn't sufficient.
Unfortunately I haven't yet found a better solution.

Related

How to Request User Data after Logged In, Node.JS

Hello guys I'm new to Node so please bear with me.
Anyway I am currently working on authentication for my new Node app. So far I was capable of getting everything basic working (login, register, logout). I am now entering the settings page of my user profile but I cannot access {{username}} or {{email}} for example, it either stays blank or throws back an error.
I was wondering how I can make the username, email and other info stay in the session or able to access it once my user is logged in, and how I can add more information to the session later (this is important as I will need to do it).
Here is my code:
Registration (not including verification)
var newUser = new User({
username: username,
email:email,
password: password
});
User.createUser(newUser, function(err, user){
if(err) throw err;
console.log(user);
req.flash('success_msg', 'You are registered and can now login');
Login:
router.post('/login',
passport.authenticate('local', {successRedirect:'/', failureRedirect:'/users/login',failureFlash: true}),
function(req, res) {
res.redirect('/');
});
I'm not sure exactly what code I need to show but if you guys need to see anything else to help me please let me know.
I'm using Node.JS with express, passport and handlebars.
Thank you!
I was wondering how I can make the username, email and other info stay
in the session or able to access it once my user is logged in, and how
I can add more information to the session later (this is important as
I will need to do it).
There is a pretty good NPM module for this use case :
https://github.com/expressjs/session
I have made quite good experiences with it.
Just install it via NPM and then :
var session = require('express-session')
This is how you use it:
// Use the session middleware
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
// Access the session as req.session
app.get('/', function(req, res, next) {
if (req.session.views) {
req.session.views++
res.setHeader('Content-Type', 'text/html')
res.write('<p>views: ' + req.session.views + '</p>')
res.write('<p>expires in: ' + (req.session.cookie.maxAge / 1000) + 's</p>')
res.end()
} else {
req.session.views = 1
res.end('welcome to the session demo. refresh!')
}
})
As i am getting from your description is that you have problem regarding sessions.
for this you can visit https://www.npmjs.com/package/session-storage to Include session-storage into your project it will help you better.

How to differentiate between an admin and a user in the api

I'm using express framework , Lets say I have this line in the API :
router.delete('/user',(req, res) => { //deleting...}
Now I want that only an Admin will be able to access this line.
In the rest of the code there are lines that only user can access like :
router.put('/post')
And lines only admin can access like:
router.put('/killDB)
what is the best way (tokens, sessions or something like that) that will be able to help me differenitate between the two?
Use password to authenticate users and then check if the user is an admin. And then simply add password logic to your route. Below I will provide my code where I just check if user is logged in (it was enough for me)
router.get('/delete', isLoggedIn, function (req, res) {
Page.collection.drop();
var page = new Page();
page.save(function (err) {
if(err) throw err;
res.redirect('/admin');
});
});
// render login form
router.get('/login', function (req, res) {
res.render('login',{ message: req.flash('error'), layout: null});
});
// process the login form
router.post('/login', passport.authenticate('local-login', {
successRedirect : '/admin', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
router.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.isAuthenticated())
return next();
// if they aren't redirect them to the home page
res.redirect('/');
}
You can use the connect-roles package to authorize your users, and then route them to those URL's which they are allowed to access.
You can also opt for passport.js, however it is more or like a authentication package where as the connect-roles just aims at to provide only the "authorization" part. And this package works well with Express.
Once you implement this package, you can use the "role" attribute to check the user's authorization level and allow them to perform their respective actions.
For eg.,
if (req.user.role === 'admin') {
router.put('/killDB)
}
You can check out the package here: https://www.npmjs.com/package/connect-roles
Hope this helps!

Passport ensureAuthenticated

I want to implement a login with Passport.js and Github Strategy. Here is the code I use:
...
/* Ensure Auth function */
function ensureAuthenticated(req, res, next){
if (req.isAuthenticated()) return next()
else res.redirect('/');
}
/* Some modules */
global.passport = require('passport');
global.GithubStrategy = require('passport-github2').Strategy;
global.util = require('util');
global.session = require('express-session');
global.bodyParser = require('body-parser');
global.methodOverride = require('method-override');
global.partials = require('express-partials');
global.request = require('request');
/* Passport */
passport.serializeUser(function(user, done){
done(null, user);
});
passport.deserializeUser(function(obj, done){
done(null, obj);
});
passport.use(new GithubStrategy({
clientID: config.githubID,
clientSecret: config.githubSecret,
callbackURL: config.githubURL
},
function(accessToken, refreshToken, profile, done){
process.nextTick(function(){
return done(null, profile);
});
}
));
app.use(partials());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(session({secret: 'keyboard cat', resave: false, saveUninitialized: false}));
app.use(passport.initialize());
app.use(passport.session());
/* Controllers */
app.get('/auth/github', passport.authenticate('github', {scope: ['user:email']}), function(req, res){});
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/' }), function(req, res){
res.redirect('/dashboard');
});
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
I search a lot in Passport.js docs, but I can't find answers for this questions:
Why, in my case, the session doesn't save the authentification? For example, if I set the ensureAuthenticated middleware on a route, it always redirect me to /, where is the login form.
Why I can't access user info from req.user to see user infos?
Why the logout route doesn't work?
Foreword/Preamble
I had the same issue with the Local Strategy. I don't have much information on the architecture of your system, however my system used the following:
PassportJS (w/ Local Strategy)
ExpressJS handling sessions & routing (w/ a Redis MemoryStore)
NodeJS server
AngularJS 2 frontend
Things to checkout
CORS played a major role with this issue.
I used the cors library to alleviate the issues I had there. In particular, I made the following change to the configuration:
let cors_config = {
origin: "http://localhost:8080",
credentials: true
};
This configures your CORS settings to include the Access-Control-Allow-Credentials: true header in the preflight requests.
Make sure your memory store & session is configured correctly
Start off by not having a specified memory store (the default is the LocalStore) and work from there onwards. I see you're not using one now, so just read on anyway.
server.use(session({
secret: "secret_c0de",
resave: false,
saveUninitialized: false//,
//store: new redis_store(redis_settings)
}));
Check to see if, when you're authenticating, that afterwards your session cookie is returned after authenticating. It should contain a key-value with a key of 'connect.sid', your session ID.
If you're not getting a session cookie, then you know that your session might not be configured correctly, in which case you should:
Check the load order of your 'use' calls
Try and identify any errors or issues with your session handling code
If you're receiving your cookie, but the isAuthenticated() check is failing in subsequent requests, this usually hints at your MemoryStore not working.
If you're initially using a LocalStore, this doesn't require much configuration, and thus again, hints to an issue with your session configuration
If you're using a store like Redis, try and listen to the Redis events (mainly 'connect' and 'error') and log the output to the console so you can see if it's working at all.
Thing's I noticed in your code
Check your serialization functions, you're serializing the entire user object.
This is more of an optimization, but have noticed people have had more success just serializing the user's ID and deserializing it.
passport.serializeUser(function(user, done){
done(null, user.id);
});
passport.deserializeUser(function(obj, done){
User.findById(obj, function(err, user){
if(err){/*code to handle error with not finding user*/}
done(null, user);
});
});
You should require the cookie-parser since sessions use a session cookie.
To answer your questions
Why, in my case, the session doesn't save the authentification?
Session creation is broken in your application, so you should try and debug this. As mentioned above, you should check if the session ID is being returned in a session cookie after your authentication request and move on from there.
Why I can't access user info from req.user to see user infos?
The user is injected after a successful deserialization attempt from Passport. In your case, the session is never created in the first place, never serialized into your MemoryStore, and thus can never be deserialized (since it doesn't exist).
Why the logout route doesn't work?
req.logout() will only work on a successful session being created.
Judging by the other code and info you provided, I assume that's what you're referring to.
Hope you find some valuable information here, feel free to comment if you'd like to chat about something in particular! :)
The best source to understand and implent the code for passport based authentication was here. check it once
https://www.coursera.org/learn/server-side-development/lecture/Clovu/passport

Get Session Variable in middleware function in express js

I am working on a website with the help of express.js and mongodb.
To use this website persistently I am using express-session v1.11.3. And, to check if a user is logged in or not, I have created a middleware function. In this function I am trying to get a session but it always returns Undefined.
When login to the router, I have an initialized session variable with user obj. as below:
//users router
router.all('/login', function(req, res) {
var db = req.db;
.....
........
//on successfully login
req.session.user = user;
res.redirect('/dashboard');
});
After I created this middleware function in the users.js router file to authenticate. Here is the code of this function:
function requiredAuthentication(req, res, next) {
if (req.session.user) {
next();
} else {
res.redirect("/users/login");
}
}
Now, I am trying to get users listing and only authenicated users get this:
router.get('/index',requiredAuthentication, function(req, res, next) {
var db = req.db;
db.collection('users').find().toArray(function(err, users) {
if (err) {
res.render('error', {error: err});
}else {
res.render('users/index', {users: users});
}
});
});
But it's not working properly.
The code parts that you've posted seems ok, the problem might be in other code that is not visible here.
First of all make sure that you've assigned the req.session.user correctly, inside the asynchronous callback where user is fetched from the db.
Also check whether the express-session middleware is included properly, don't forget to use(session) middleware, like:
var express = require('express');
var session = require('express-session');
var app = express();
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}));
Also, try to include and use(express-cookies) middleware, this was an issue in previous express versions, but now it's used automatically by express-session.
a good practice would be to also check if a session exists before you check if a user is added to a session var.
if (req.session && req.session.user) {
//do something
}
that could also indicate more on where the problem lays.
also . try putting the function in the controller and see if it still doesn't work for you. (instead of passing it in the route)
check to see if you correctly applied all the modules needed (express-session - and is up to date and not corrupted. (update to latest version if no)
make sure your middleware is set correctly
app.use(session({
secret: '',
saveUninitialized: true/false,
resave: true/false
}));
if you did checked all of the above, take a look at the console of your server. does it output any error regarding the session var or decleration.? if so please post
hope this guide you somehow. cheers

Expressjs passport-local can't logout

I copy pasted the app passport-local on my app,
The fun is I can log in users, but I can't make them logout,
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
this is not doing nothing, nothing on the logfiles, and I I have its a link to /logout
this is the main route examples
app.get('/page1', function(req, res){
res.render('page1', {user: req.user});
});
app.get('*', function(req,res){
res.render('root', {user: req.user});
});
Why the logout its not working ????
Apparently this is a known problem:
Why is PassportJS in Node not removing session on logout
The thread mentioned above suggests to use req.session.destroy() instead.
It would be nice to have some feedback from the Passport team directly.
This is still an issue.
What I did was to use req.session.destroy(function (err) {}); on the server side and on the client side, whenever they logout:
const logout = () => {
const url = '/users/logout'
fetch(url)
setTimeout(function () {
location.reload(); }, 500);
That way, when refreshing the page, the user is without session. Just make sure you are redirecting to the correct page if no one is authenticated.
Not the best approach, perhaps, but it works.

Categories