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.
Related
I'm currently using Node.js along with Mongoose, Express-Session and Passport. This is all to develop the Authentication side of my project.
Today I ran into an issue: When trying to login an user, their Id is undefined.
Here is the piece of code that I think is related to this issue:
var User = require("./user-model"); //user model located in another file
var passport = require("passport");
var session = require("express-session");
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.post("/register", (req, res) => {
var user = new User(req.body);
user.save()
.then(item => {
User.findOne().sort({_id: 1}).exec(function(err, results, fields) {
if(err) throw err;
const user_id = results[0]; //Here, the console logs "undefined"
console.log(results[0]);
req.login(user_id, function(err) {
console.log("User saved to database");
res.redirect('/');
});
});
})
.catch(err => {
console.log(400).send("Unable to save to database");
});
});
passport.serializeUser(function(user_id, done) {
done(null, user_id);
});
passport.deserializeUser(function(user_id, done) {
done(null, user_id);
});
If you need any extra piece of code, I'll glady update this for you.
Thanks in advance!
findOne finds one result, hence the name.
So results parameter is in fact user, and it likely should be:
User.findOne().sort({_id: 1}).exec(function(err, user) {
if(err) throw err;
const user_id = user._id;
...
Note that user can be null in case there's none.
I'm at a complete loss with this. I can only assume it's passport because when I comment out its initialization everything magically works again. I have 2 routes currently setup. One is a get request that requires a user to be logged in and the other is a post request that actually does the logging in.
in my app.js file my passport setup looks like this:
var sessionStore = new MySQLStore(options);
//handles cookie/session creation
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: config.sessionKey,
resave: false,
store:sessionStore,
saveUninitialized: false,
cookie: {
//secure: true,
maxAge:24*60*60*1000 //1 day in milliseconds
}
}));
app.use(passport.initialize());
app.use(passport.session());
require('./services/passport');
//initiate route handlers
app.use('/login', require('./routes/authRoutes'));
app.use('/tiles', require('./routes/tileRoutes'));
I am using a local strategy as my users won't be using any kind of social app to login. I configured another file passport.js to actually handle the passport setup. I am using the sequelize database in order to verify users. This whole process looks like this:
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
passport.serializeUser((user, done)=>{
done(null, user.id);
});
passport.deserializeUser((user, done)=>{
done(null, user.id);
});
passport.use(new LocalStrategy(function (username, password, done) {
const db = require('../models');
db.user.findOne({ where: {username: username} })
.then((user)=>{
if(!user){
return done(null, false);
}else{
// if(user.dataValues.hashed_password === password)
// return done(null, user);
bcrypt.compare(password, user.dataValues.hashed_password, function(err, res) {
if(res === true){
return done(null, user);
}else{
return done(null, err);
}
});
}
});
}));
signing people up, creating the session, and storing the session id all seem to be happening, however when I start making get/post requests on my front end with axios (I'm using react) I continually get a internal server error. I've tried catching this in everyway I can think of, breaking at definite points, an console.logging out but I just get the same message:
[0] GET /tiles 500 8.049 ms - 2
[0] GET /tiles 500 2.757 ms - 2
or from the console
GET http://localhost:3000/tiles/ 500 (Internal Server Error)
the actual get request looks like this (i havent' put much in till I know it works):
const express = require('express');
const router = express.Router();
router.get('/', isLoggedIn, (req, res)=>{
debugger;
res.send({hi:'there'})
});
function isLoggedIn(req, res, next) {
debugger;
if (req.isAuthenticated()) return next();
console.log('failure');
res.redirect('/login')
}
module.exports=router;
The deserializeUser should call the function to find user by id here, then pass it to done, the first param is userId as serializeUser return the user.id to session store. Example:
passport.deserializeUser((id, done)=>{
passport.deserializeUser((id, done) => {
User.findById(id).then((user) => {
done(null, user);
}).catch(done);
});
});
I am trying to display a page to the user(front-end), which shows them their login history but am a bit confused and need some help please.
Am using Express, and storing my sessions in mongoStore like this:
app.use(session({
secret: process.env.SECRET,
key: process.env.KEY,
resave: false,
saveUninitialized: false,
store: new MongoStore({
mongooseConnection: mongoose.connection
})
}));
and my login strategy is local which is located in my userController file like this:
exports.login = passport.authenticate('local', {
failureRedirect: '/login',
failureFlash: 'Failed Login!',
successRedirect: '/',
successFlash: 'You are now logged in!'
});
meanwhile I have a helpers function in my handlers like this:
const passport = require('passport');
const mongoose = require('mongoose');
const User = mongoose.model('User');
const sess = passport.use(User.createStrategy()); // creates strategy during every user login
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
I know that I have to create a token during each user login but i believe that is already creeated using the serialized function(in this case it's email) but then how can i keep track of the users so that during their next session, I can retrieve the date they last logged in and render(show) it to them? Authentication and login works perfect, i just need to know how I am going to display their previous logins any other future time they are signed in and click to a specific route (not homepage). thanks in advance
You should implement a customized authenticate handler to store the data you need when a user successful login,
Router.post('/login', (req, res, next)=>{
passport.authenticate('local', function(err, user) {
if (err) { //do something when user failed to authenticate... }
if (!user) { //do something when user not found }
//explicitly call req.logIn, then save related information you need
req.logIn(user, function(err) {
if (err) { //do something when user failed to login }
//for example, save the current time to user collection
Users.updateLastLoginTime(user.id, Date.now()).then(function(){
//when all set, send the response
return res.json(true);
})
});
})(req, res, next);
})
In addition to #MarkoCen's answer, something like this will perhaps be the method in your Users model:
// set a time for the login
UserSchema.statics.updateLastLoginTime = function(userId, currentTime, callback) {
User.findOne({ _id: userId }, 'lastLoginTime')
.exec(function(error, user){
if (error) {
return callback(error);
} else if(!userId) {
var err = new Error('User not found.');
err.status = 401;
return callback(err);
}
user.lastLoginTime = currentTime;
});
};
When im logging in the req.user is displayed as it should, but after navigating to /test , the req.user is undefined.
Why is that?
server.js
var express = require('express'); // call express
var app = express(); // define our app using express
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var session = require('express-session');
var router = express.Router();
var Account = require('src/app/models/Users.js');
var Core = require('/src/app/gamemodels/core');
// Init passport authentication
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
require('/src/config/passport')(passport);
var cookieParser = require('cookie-parser')
// required for passport session
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
mongoose.connect('DB');
app.use(cookieParser()) // required before session.
app.use(session({ secret: 'xxxx' }));
app.use(passport.initialize());
app.use(passport.session());
var port = process.env.PORT || 3000; // set our port
// test route to make sure everything is working (accessed at GET http://localhost:8080/api)
router.get('/', function(req, res) {
res.json({ text: 'hooray! welcome to our api!' });
});
router.get('/test', function(req,res) {
console.log(req);
console.log(req.user);
res.json(req.user);
});
router.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
}));
router.post('/login', passport.authenticate('local-login'), function(req, res) {
console.log("executed login!");
console.log(req.user);
req.session.user = req.user;
});
});
*/
// more routes for our API will happen here
// REGISTER OUR ROUTES -------------------------------
// all of our routes will be prefixed with /api
app.use('/api', router);
// START THE SERVER
// =============================================================================
app.listen(port);
console.log('Magic happens on port ' + port);
passport js:
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var Account = require('src/app/models/Users.js');
// expose this function to our app using module.exports
module.exports = function(passport) {
passport.serializeUser(function(user, done) {
done(null, user);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
Account.findById(id, function(err, user) {
done(err, user);
});
});
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
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
console.log("doing local login");
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
Account.findOne({ 'username' : username }, function(err, user) {
var thisuser = user;
console.log("query account is done");
// if there are any errors, return the error before anything else
if (err) {
console.log("error occured");
return done(err);
}
console.log("if user exist check");
// if no user is found, return the message
if (!user)
return done(null, false,'No user found.'); // req.flash is the way to set flashdata using connect-flash
console.log("checking password");
// if the user is found but the password is wrong
if (!user.validPassword(password)) {
console.log("password is not valid");
return done(null, false, 'Oops! Wrong password.'); // create the loginMessage and save it to session as flashdata
}
console.log("all good! logging in!");
req.login(thisuser, function(error) {
if (error) return next(error);
console.log("Request Login supossedly successful.");
});
// all is well, return successful user
return done(null, thisuser);
});
}));
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done) {
process.nextTick(function() {
console.log("doing local signup");
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
Account.findOne({ 'username' : username }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, 'That username is already taken.');
} else {
var newUser = new Account();
// set the user's local credentials
newUser.username = username;
newUser.password = newUser.encryptPassword(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
};
EDIT1:
changed passport.js serialize function and deserialize function to the following:
passport.serializeUser(function(user, done) {
done(null, user.username);
});
// used to deserialize the user
passport.deserializeUser(function(username, done) {
Account.findOne({'username': username}, function(err, user) {
done(err, user);
});
});
still did not make any difference. Undefined still occur.
EDIT2:
value of user in serializing:
{ _id: 5909a6c0c5a41d13340ecf94,
password: '$2a$10$tuca/t4HJex8Ucx878ReOesICV6oJoS3AgYc.LxQqCwKSV8I3PenC',
username: 'admin',
__v: 0,
inFamily: false,
bank: 500,
cash: 2500,
xp: 0,
rank: 1,
bullets: 0,
location: 1,
permission: 0,
health: 100 }
edit3:
changed the login func to:
router.post('/login', passport.authenticate('local-login'), function(req, res) {
console.log("executed login!");
console.log(req.user);
req.session.user = req.user;
req.logIn(req.user, function (err) {
if (err) {
return next(err);
}
});
});
server log response:
doing local login
query account is done
if user exist check
checking password
all good! logging in!
serializing!
Request Login supossedly successful.
serializing!
executed login!
{ _id: 5909a6c0c5a41d13340ecf94,
password: '$2a$10$tuca/t4HJex8Ucx878ReOesICV6oJoS3AgYc.LxQqCwKSV8I3PenC',
username: 'admin',
__v: 0,
inFamily: false,
bank: 500,
cash: 2500,
xp: 0,
rank: 1,
bullets: 0,
location: 1,
permission: 0,
health: 100 }
serializing!
still no sign to unserialize log.
The reason is that you are missing on the deserialization part.
/**
* Each subsequent request will contain a unique
* cookie that identifies the session. In order to support login sessions,
* Passport will serialize and deserialize user instances to and from the session.
*/
passport.serializeUser(function (user, done) {
done(null, user.username);
});
passport.deserializeUser(function (username, done) {
/**
* Necessary to populate the express request object with
* the 'user' key
* Requires(*):
* - session support with express
* - call to logIn from passport.auth)enticate if using authenticate
* callback.
*/
// TODO: Verify if username exists
done(null, username);
});
So after the user is authenticated or when req.isAuthenticated() is true, the deserialization middleware function will be invoked and will update the request object with username or req.user in your case.
Reference:
passport js sessions
Since you are using a custom callback to handle success or failures, it becomes the application's responsibility to establish a session by calling req.logIn. So after the user is authenticated, add
req.logIn(user, function (err) {
if (err) { return next(err); }
return { // Do a redirect perhaps? }
});
Refer to section Custom Callbacks in the reference link, I gave you.
When defining methods and middleware on the passport object, order matters. Your code is pretty entangled. A little decoupling will go a long way here.
Move all of the strategy logic out of server.js and your passport.js. Put it in its own set of files. Also, you don't need to include the base Strategy in the server file.
Define an express router in a separate file and mount the routes in your server.js
passport.initialize() and passport.session() middlewares need to attach to your express app instance before you define serialize and deserialize.
No need to set req.session.user, which defeats the purpose of storing just the user id in the session. On each request to express, once you deserialize the user by reading the id in req.session.passport.user, you're loading the entire user account document into req.user and you can access all user data directly from req.user.
If you're using a pre-packaged passport Strategy constructor which calls done(), you don't need to call req.login anywhere.
server.js
//express, body parser, express session, etc
const app = express();
const passport = require('passport');
const user = require('./passport-serialize');
const routes = require('./routes');
//lots of middleware
//session middleware
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(user.serialize);
passport.deserializeUser(user.deserialize);
app.use('/api', routes);
//actual server, more stuff
passport-serialize.js
//include Account model
const user = {
serialize: (user, done) => {
done(null, user.username)
},
deserialize: (username, done) => {
Account.findOne({'username': username}, function(err, user) {
done(err, user);
}
}
module.exports = user;
routes.js
const express = require('express');
const router = new express.Router();
const passport = require('./strategies');
//many routes
router.post('/login', passport.authenticate('local-login'), function(req, res) {
console.log("executed login!");
console.log(req.user);
});
router.get('/test', function(req, res) {
console.log(req.user);
res.json(req.user);
});
module.exports = router;
strategies.js
const passport = require('passport');
const LocalStrategy = require('whatever the package is');
//Account model
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
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
console.log("doing local login");
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
Account.findOne({ 'username' : username }, function(err, user) {
var thisuser = user;
console.log("query account is done");
// if there are any errors, return the error before anything else
if (err) {
console.log("error occured");
return done(err);
}
console.log("if user exist check");
// if no user is found, return the message
if (!user)
return done(null, false,'No user found.');
// req.flash is the way to set flashdata using connect-flash
console.log("checking password");
// if the user is found but the password is wrong
} else if (!user.validPassword(password)) {
console.log("password is not valid");
return done(null, false, 'Oops! Wrong password.');
// create the loginMessage and save it to session as flashdata
}
console.log("all good! logging in!");
// all is well, return successful user
return done(null, thisuser);
});
}));
module.exports = passport;
First of all, I'm very new to Passport.js so maybe this ends up being a very naïve question. I have this as a strategy for the signup:
// Configuring Passport
var passport = require('passport');
var expressSession = require('express-session');
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook');
app.use(expressSession({secret: 'mySecretKey'}));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
//[...]
passport.use('signup', new LocalStrategy({
name : 'name',
password : 'password',
email : 'email',
passReqToCallback : true
},
function(req, username, password, done) {
findOrCreateUser = function(){
// find a user in Mongo with provided username
User.findOne({'name':username},function(err, user) {
// In case of any error return
if (err){
console.log('Error in SignUp: '+err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists');
return done(null, false,
req.flash('message','User Already Exists'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.name = name;
newUser.password = createHash(password);
newUser.email = req.param('email');
/*newUser.firstName = req.param('firstName');
newUser.lastName = req.param('lastName');*/
// save the user
newUser.save(function(err) {
if (err){
console.log('Error in Saving user: '+err);
throw err;
}
console.log('User Registration succesful');
return done(null, newUser);
});
}
});
};
// Delay the execution of findOrCreateUser and execute
// the method in the next tick of the event loop
process.nextTick(findOrCreateUser);
})
);
And this is how I deal with a POST on /register:
/* Handle Registration POST */
app.post('/register', passport.authenticate('signup', {
successRedirect: '/',
failureRedirect: '/failure_registration',
failureFlash : true
}));
This always brings me to the failureRedirect link, instead of the success. The input data is correct, and I'm always using a different user and mail to register. I'm sorry if this is a stupid question, but I don't really understand why it never goes to the successRedirect.
Thanks.
EDIT: added the suggestion and corrections by #robertklep, still not working. I'd like to point out that no error is triggered, nor any log printed.
EDIT2: Serialization/deserialization functions:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
I had this same problem, failureRedirect was always being executed
First to diagnose, I used the custom callback method
http://passportjs.org/docs/authenticate
app.post('/sign_in', function(req, res, next) {
console.log(req.url);
passport.authenticate('local-login', function(err, user, info) {
console.log("authenticate");
console.log(err);
console.log(user);
console.log(info);
})(req, res, next);
});
Then I could see what the hidden error was
authenticate
null
false
{ message: 'Missing credentials' }
Which then became easy for me to diagnoise, I was sending JSON in the request rather than FORM fields
Fixed by changing
app.use(bodyParser.urlencoded({ extended: true }));
To
app.use(bodyParser.json());
I know this is almost 4 years old, but in case someone runs into the same issue, I used the diagnose from djeeg
app.post('/sign_in', function(req, res, next) {
console.log(req.url);
passport.authenticate('local-login', function(err, user, info) {
console.log("authenticate");
console.log(err);
console.log(user);
console.log(info);
})(req, res, next);
});
and I got:
null
false
'missing credentials'
THE SOLUTION: It turns out I had not used the "name" in my HTML form which meant data wasn't read and that's where the error came from
<input name="email" type="email">
That fixed it for me
A couple of points:
the function function(req, name, password, email, done) is wrong. the verify function signature is function(req, username, password, verified) when the passReqToCallback flag is on.
you're not providing serialization/deserialization functions. This is probably not biting you at the moment but could later on.
passport.serializeUser(function(user, done){
done(null, user.id);
});
what I also find interesting is that you use the authenticate function to actually create a user. I'd probably create the user and then call passport.login to make them authenticated.
Just my 2 pence :-)
do you use http or https ? I had the same situation. I fixed it like this
app.use(expressSession({....
cookie: {
httpOnly: true,
secure: false // for http and true for https
}
}));
In my situation passport can't receive cookies.
Try checking method of your form, it must be method = post, in case i mistakenly wrote it as GET method, and took me 3 days to find this, because there was no error