Recently I've found tricki bug in my express js project and I would be grateful for help with debugging it.
This is related question, but answer is not resolving problem, because I want to specify maxAge and deleting this parameter from session configuration is not solution for me.
I am using Passport js + express-session
This is my session configuration:
var passport = require('passport');
var session = require('express-session');
var cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(session({
secret: 'MySecret',
resave: false,
saveUninitialized: false,
cookie : {
secure: false, // If it's true, login is not working as well
maxAge: new Date(Date.now() + (60 * 1000 * 30))
}
}));
app.use(passport.initialize());
app.use(passport.session());
To check that user is authenticated I'm using simple middleware:
middlewareObj.isLoggedIn = function(req, res, next) {
if (req.isAuthenticated()){
return next();
} else {
return res.redirect('/signin');
}
};
Passport local-signin:
passport.use('local-signin', new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function (req, email, password, done) {
var User = user;
var isValidPassword = function (userpass, password) {
return bCrypt.compareSync(password, userpass);
}
User.findOne({
where: {
email: email
}
}).then(function (user) {
if (!user) {
return done(null, false, req.flash('error', 'User does not exists'));
}
if (!isValidPassword(user.dataValues.userPassword, password)) {
return done(null, false, req.flash('error', 'Incorrect password.'));
}
return done(null, {user});
}).catch(function (error) {
return done(null, false, req.flash('error',error.message));
});
}
));
This is my logout route:
router.get('/logout',(req,res) => {
req.session.destroy(function(err) {
return res.redirect('/');
});
});
Standard user login is working fine, after submitting sign in form user is serialized than deserialized and isAuthenticated() method returns true.
But cookie configuration(maxAge exactly) is making some problems. When cookie lifetime expired, next user's login is not working anymore. isAuthenticated() method is returning false (user object does't exist inside req).
After restarting server, user can login normally, but when cookie maxAge is reached, next login is not working again. Passport is not showing any errors.
It likely has something to do with your maxAge value. The expressjs/sessions package docs point out that maxAge is "the number (in milliseconds) to use when calculating the Expires Set-Cookie attribute".
You're passing a Date to maxAge; it's expecting the number of milliseconds until the cookie expires.
Related
My PassportJS setup doing something weird, I can see req.user after logged-in in the deserialize function but after all req.isAuthenticated() false and no req.user found. I have already referred a bunch of questions regarding this issue on StackOverflow, almost every question on StackOverflow. Nothing works for me, not sure what is the case here. I'm posing my code, can someone pls tell me what is wrong here and why it's happening. At least a fix! :(
I'm using passport-google-oauth as my strategy. Also, the client is an Angular app.
I also tried with passport-google-oauth20 by accessing directly.
What I found is, in social_logins.google_callback the req.user can
be found and also req.isAuthenticated() returns true. After the
redirect happens it won't work.
Thanks in advance!
// app.js
const pgSession = require('connect-pg-simple')(session);
app.set('trust proxy', 1);
/** #type {session.CookieOptions} */
const cookieOptions = {
path: '/',
secure: false,
httpOnly: false,
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
};
app.use(session({
secret: require('./config').session.secret, // session secret
cookie: cookieOptions,
proxy: true,
name: config.session.name,
resave: false,
saveUninitialized: true,
store: new pgSession({
pool: db.pool,
tableName: 'sess'
}),
}));
require('./config/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
// passport.js
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser(async (user, done) => {
done(null, user);
});
// passport login.js
app.get('/social-logins/connect/google', passport.authenticate('google-login', {
successRedirect: '/social-logins/google',
failureRedirect: '/social-logins/google',
scope: ['profile', 'email'],
failureFlash: true,
}));
// routes/index.js
router.get('/social-logins/google', social_logins.google_callback);
// callback funtion
social_logins.google_callback = async (req, res) => {
try {
const { user } = req;
if (!user) return res.redirect('https://localhost:3000/auth/login?message=Login Failed');
const url = `https://localhost:3000/auth/dashboard`;
req.session.save(() => {
return res.redirect(url);
});
} catch (error) {
log_error(error);
res.redirect(`https://localhost:3000/auth/login?error=true`);
}
};
// passport strategy
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
module.exports = new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: 'https://localhost:3000/social-logins/connect/google',
passReqToCallback: true
}, async (req, accessToken, refreshToken, profile, done) => {
try {
const acc = {}; // find from db and assign db.query('select * from users where id = $1', [profile.id]);
return done(false, acc, { message: 'User successfully logged in' });
} catch (error) {
return done(error);
}
});
I resolved this by setting domain to cookieOptions.
I am a beginner Node.js developer and am working on a web app for which I need session management. I'm defining Express session like the following:
app.use(cookieParser());
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: true,
cookie: {
expires: 600000
}
}));
And set the session variable in /signup (not shown) and /login. However, if I try to access req.session in any other route, it shows up as "undefined". Does anyone have any input on this?
router.post('/login', async function(req, res) {
console.log(req.body);
var email = req.body.email,
password = req.body.password;
let user = await User.findOne({ email: email });
if (!user) {
res.status(400).send("Failure");
} else if (!bcrypt.compareSync(req.body.password, user.password)) {
res.status(400).send("Failure");
} else {
req.session.user = user;
res.status(201).send("Success");
}
});
I'm having an issue with Passport.js using the ActiveDirectory strategy where I'm successfully authenticating but fail to maintain a session on subsequent requests. After authentication, any call to req.isAuthenticated() returns false. Additionally, no cookie is being set on the client's browser. I've spent the day pouring over similar posts but so far none of the suggested solutions have worked.
Some observations to note:
-Authentication is always successful and returns the req.user to client.
-The console.log in serializeUser() always fires and correctly logs the user record.
-The console.log in deserializeUser() never fires, I'm not sure when this function is supposed to be called but it doesn't appear to be?
-After logging in, each time I make a call to my /test endpoint the non-authenticated condition fires.
// Passport Requires
const passport = require('passport');
const session = require('express-session');
var ActiveDirectoryStrategy = require('passport-activedirectory');
// Setup
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: false,
cookie: { secure: false, maxAge: 600000 }
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
console.log('userStrategy -- serialized:', user)
done(null, user);
});
passport.deserializeUser(function(user, done) {
console.log('userStrategy -- deserializeUser', user)
done(null, user);
});
passport.use(new ActiveDirectoryStrategy({
integrated: false,
passReqToCallback: true,
ldap: {
url: "url",
baseDN: "baseDN",
username: `username`,
password: `password`
}
}, function (req, profile, ad, done) {
ad.isUserMemberOf(profile._json.dn, 'Access Group', function (err, isMember) {
console.log('isMember:', isMember)
if (err) {
return done(err)
} else {
return done(null, profile)
}
})
}))
// Login Route
app.post('/login',
passport.authenticate('ActiveDirectory', { failWithError: true }),
function (req, res) {
console.log('Authenticated');
return res.status(200).send(req.user);
}, function (err) {
console.log('Not Authenticated');
return res.sendStatus(401).send(err);
}
)
// Test endpoint to check whether user is authenticated
app.get('/test', function(req, res) {
if (req.isAuthenticated()) {
res.send('Youre authenticated!')
} else {
res.send('Youre not authenticated!')
}
})
Appreciate any thoughts on the problem -- thanks!
I've figured out the issue which has nothing to do with Passport or server setup.
In my client-side API call to login I've been using fetch() but I did not have credentials: 'include' option on the initial POST to login. Once I added this option, a cookie was sent back to the client browser and future API calls to isAuthenticated() returned true.
Make sure to have credentials: 'include' on ALL of your API calls, including the initial POST to login.
I've added nodejs passport login to my app and everything worked fine, until I committed changes to production. The issue is pretty wired: user is randomly changes sometimes when I reload the page.
Here is my app.js code:
var mysql = require('promise-mysql');
var passport = require('passport');
var app = express();
app.use(cookieParser())
app.use(session({
secret: 'keyboard cat',
maxAge: 60 * 5,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize());
app.use(passport.session());
mysql.createConnection(dbConfig.connection).then(
function (connection) {
require('./config/passport')(passport, connection); // pass passport for configuration
}
);
Here is what I have in configs/passport.js
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt-nodejs');
module.exports = function (passport, connection) {
passport.serializeUser(function (user, done) {
done(null, user.name);
});
// used to deserialize the user
passport.deserializeUser(function (name, done) {
connection.query("SELECT * FROM users WHERE name = ? ", [name])
.then(function (rows) {
done(null, rows[0]);
})
.catch(function (err) {
console.log("Error getting user form DB: ", err);
done(err);
});
});
passport.use(
'local-login',
new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function (req, username, password, done) { // callback with email and password from our form
connection.query("SELECT * FROM users WHERE username = ?", [username])
.then(function (rows) {
if (!rows.length) {
done(null, false); // req.flash is the way to set flashdata using connect-flash
}
// if the user is found but the password is wrong
else if (!bcrypt.compareSync(password, rows[0].password)) {
done(null, false); // create the loginMessage and save it to session as flashdata
// all is well, return successful user
}
else {
done(null, rows[0]);
}
})
.catch(function (err) {
console.log("Login Failed: ", err.body);
done(err);
});
})
);
};
And this is what I have in every route file:
router.all('*', function (req, res, next) {
if (req.isAuthenticated()) {
user.init(req.user);
next(); // pass control to the next handler
}
else {
res.redirect('/');
}
});
Does anyone had similar issue? Seems like I've made some simple and stupid error, because google can't find similar issues.
Thanks!
You're executing two different queries:
// passport.deserializeUser()
connection.query("SELECT * FROM users WHERE name = ? ", [name])
// In the Passport verification handler
connection.query("SELECT * FROM users WHERE username = ?", [username])
My guess would be that name isn't unique, and that you want to use username everywhere.
As an aside: you're setting maxAge to 300 milliseconds.
Turns out to be issue with objects storing by node js in memory. I don't fully understand how this is happened, but this is what I found.
I stored req.user in userModel object, and if there are a lot of requests on server, this userModel object sometimes gets messed up with data from different users.
The solution was to directly user req.user everywhere.
I am upgrading the express 4 and my passport is failing every time now. It is not even logging to the console in passport.use(new LocalStrategy.
It redirects the /failure every time without hitting any breakpoints
// Use the LocalStrategy within Passport.
// Strategies in passport require a `verify` function, which accept
// credentials (in this case, a username and password), and invoke a callback
// with a user object. In the real world, this would query a database;
// however, in this example we are using a baked-in set of users.
passport.use(new LocalStrategy(
function(username, password, done) {
console.log("LocalStrategy working...");
// asynchronous verification, for effect...
process.nextTick(function() {
// Find the user by username. If there is no user with the given
// username, or the password is not correct, set the user to `false` to
// indicate failure and set a flash message. Otherwise, return the
// authenticated `user`.
findByUsername(username, password, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Unknown user ' + username
});
} else {
return done(null, user);
}
})
});
}
));
app.use(cookieParser('keyboard cat'));
app.use(session({
secret: 'keyboard cat',
saveUninitialized: true,
resave: true
}));
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.post('/login', passport.authenticate('local', {
failureRedirect: '/failure',
failureFlash: false
}),
function(req, res) {
res.cookie('userdata', req.user);
switch (req.user.role) {
case 'candidate':
res.redirect('/app/candidates');
break;
case 'employer':
res.redirect('/app/employers');
break;
case 'provider':
res.redirect('/app/providers');
break;
case 'admin':
res.redirect('/app/admin');
break;
default:
break;
}
});
Assuming you have all relevant code included the reason for the failure is likely missing body parser. The authentication strategy will try to find the username and password fields from req.body and req.query, and if there is no body parser used req.body will be empty. The strategy will then fail straight away as it would have nothing to pass to your verify callback.
You need to make the Express application use relevant body parser, for example:
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
Have you included LocalStrategy?
var LocalStrategy = require('passport-local').Strategy;
app.use(express.cookieParser()); // read cookies
app.use(express.bodyParser()); // get information from html forms