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
Related
I'm having trouble with Passport.JS inside my Express app, more specifically the successful redirect for the registration/login. The username/password is being stored in DB without problems but the redirect in both cases is not working.
I'm using: Express, Body-Parser,, Mongoose, Passport, Passport-local and Passport-local-mongoose.
Passport Config
app.use(require("express-session")({
secret: "This is a test app",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser);
passport.deserializeUser(User.deserializeUser);
Sign Up Route
app.post("/register", function(req, res) {
var newUser = new User({username: req.body.username});
User.register(newUser, req.body.password, function (err, user) {
if (err) {
console.log(err);
return res.render("register");
}
passport.authenticate("local"),(req, res, function(){
res.redirect("/campgrounds");
});
});
});
Login Route
app.post("/login",
passport.authenticate("local", {
successRedirect: "/campgrounds",
failureRedirect: "/login"
}));
Write this in your passport config
and try to match variables names to yours in your application
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'usernameFORM',
passwordField : 'passwordFORM',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, usernameCB, passwordCB, done) {
// asynchronous
process.nextTick(function() {
// Whether we're signing up or connecting an account, we'll need
// to know if the email address is in use.
User.findOne({'username' : usernameCB}, function(err, existingUser) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if there's already a user with that email
if (existingUser)
return done(null, false);
// If we're logged in, we're connecting a new account.
if(req.user) {
console.log(req.user);
var user = req.user;
User.username = usernameCB;
User.password = user.generateHash(passwordCB);
User.save(function(err) {
if (err)
throw err;
return done(null, user);
});
}
// We're not logged in, so we're creating a brand new user.
else {
// create the user
var User = new User();
User.username = usernameCB;
User.password = User.generateHash(passwordCB);
User.save(function(err) {
if (err)
throw err;
return done(null, user);
});
}});
});
}));
and in your routes
app.post('/signup',passport.authenticate('local-signup',{
successRedirect:'/campgrounds',
failureRedirect:'/login'
}))
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;
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 have been using NodeJS to create a REST API service. I don't need to render any webpages or 'ejs' files.
With that In mind I need to know if there is an arquitecture to save JSON templates like the 'views' folder for storing '.ejs' templates to use them as response for let's say a profile.
Also, I have been working with passport module to authenticate users and I cannot pass the flash message to a JSON response, so far this is how it looks:
passport.use('local.signup', new LocalStrategy({
usernameFiled : 'email',
passwordField : 'password',
passReqToCallback : true
}, function(req, res, password, done){
User.findOne({'email' : email}, function(err, user){
if (err)
return done(err);
if (user)
return done(null, false, {message : 'Email already in use'});
var newUser = new User();
newUser.email = email;
newUser.password = newUser.encryptPassword(password);
newUser.save(function(err, result){
if (err)
return done(err);
return done(null, newUser);
});
});
})) ;
And this is my routing :
app.post('/signup', passport.authenticate('local.signup', {
successRedirect : '/me',
failureRedirect : '/loginerr',
failerFlash : true
}));
/*===== SECURE ==== */
app.get('/me', function(req, res, next){
res.end('Welcome user');
});
/* ========== Errors ====================== */
app.get('/loginerr', function(req, res, next){
var message = req.flash('error');
res.setHeader('Content-Type', 'application/json');
res.json({'error' : 'email already taken'}); //Here i should use the flash message instead.
});
As you can see, if there is an error when the user signs up, it is redirected to a /loginerr get, Instead of that i want to respond with a template of a JSON to respond with the flash message.
How can I do such thing?
Maybe you can need use JSON.stringify()
for example
app.get('/loginerr', function(req, res, next){
res.setHeader('Content-Type', 'application/json');
res.end(`{error:${req.flash('error')}}`)
});
Good Luck