passport.js - authenticate users from MongoDB with passport-local - node.js

I have a simple users collection in my MongoDB. I user mongo-native driver.
{
"email": "johndow#example.com",
"password": "123456",
"_id": {
"$oid": "50658c835b821298d3000001"
}
}
As I user auth via pair email:pass, I re-wrote default passport-local function findByUsername to this:
function findByEmail(email, fn) {
db.collection("users", function(err, collection) {
collection.find({}, {}, function(err, users) {
users.each(function(err, user) {
if (user.email === email) {
return fn(null, user);
}
});
return fn(null, null);
});
});
}
Just get all of the users form DB, and checking - if user.email == provided email, then return user object.
I use _id parameter of MongoDB as id for users, that's why I've modifies these two functions:
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
findById(id, function(err, user) {
done(err, user);
});
});
And this is my code for passport local strategy:
passport.use(new LocalStrategy( function(email, password, done) {
process.nextTick(function() {
console.log('initialize findByEmail() for "',email,'"');
findByEmail(email, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
console.log('Unknown user ' + email)
return done(null, false, {
message: 'Unknown user ' + email
});
}
if (user.password != password) {
console.log('Invalid password')
return done(null, false, {
message: 'Invalid password'
});
}
//сonsole.log('ALL VERIFIATION PASSED');
return done(null, user);
})
});
}));
I post data from login page:
app.post('/login', passport.authenticate('local', {
failureRedirect: '/',
failureFlash: true
}), function(req, res) {
res.redirect('/desk');
});
And I get
Error: Can't set headers after they are sent.
and after this I get
TypeError: Cannot read property 'email' of null
The last error is really strange 'cause findByEmail has console.log(user) line (removed from this listing) and it list all the user's data.
What am I doing wrong?

It's not well documented, but cursor.each provides a null value to the second parameter of its callback to indicate that the cursor has no more documents available. It's only mentioned in the example of the documentation.
So in your case you should be checking for user !== null in your users.each callback.
However, it would be more efficient to have mongo do the searching for you by changing your find call to:
collection.findOne({email: email}, {}, function(err, user) {
if (user) {
// email was found case
...
}
...
}

Related

What is wrong with this ExpressJS + Passport signup route? It returns 400 Bad Request

I implemented a signup route below. It gets to "User saved..." but the request returns 404.
It doesn't seem to be executing the login strategy:
router.post("/signup", function(req, res, next) {
var email = req.body.email;
var password = req.body.password;
User.findOne({ email: email }, function(err, user) {
if (err) { return next(err); }
if (user) {
return res.status(409).send({message: "Duplicate user - already registered."});
}
var newUser = new User({
email: email,
password: password
});
newUser.save(next);
console.log("User saved...");
});
},
passport.authenticate("login"),
function(req, res) {
return res.status(200).send({
message: "Signup successful",
user: req.user
});
}
);
My Passport login strategy looks like this:
passport.use("login", new LocalStrategy(async (email, password, done) => {
console.log("login...");
User.findOne({ email: email }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: "No user has that email!" });
}
user.checkPassword(password, function(err, isMatch) {
console.log("Checked password...");
console.log("Error? Match?");
console.log(err);
console.log(isMatch);
if (err) { return done(err); }
if (isMatch) {
console.log("Returning done...");
return done(null, user, { message: 'Logged in Successfully' });
} else {
return done(null, false, { message: "Invalid password." });
}
});
});
}));
Here's what I see in the logs:
User saved...
POST /signup 400 181.122 ms - -
Passport is likely throwing the 400 error because the username/password fields are not set.
Passport expects username and password and what you are passing are the email and password. So you can modify the code and let passport's LocalStrategy use the email as the username.
You can set the username and password as follows:
passport.use("login", new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
},async (usernameField, passwordField, done) => {
console.log("login...");
User.findOne({ email: usernameField }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: "No user has that email!" });
}
user.checkPassword(passwordField, function (err, isMatch) {
console.log("Checked password...");
console.log("Error? Match?");
console.log(err);
console.log(isMatch);
if (err) { return done(err); }
if (isMatch) {
console.log("Returning done...");
return done(null, user, { message: 'Logged in Successfully' });
} else {
return done(null, false, { message: "Invalid password." });
}
});
});
}));
You can check the line which was throwing the error from passport's source code here

Passport.js positive result

I am attempting to authenticate a user in a Node app using Sequelize and Passport. I am able to hit my database but can't seem to get a positive result. Basically, I have a simple frontend form that accepts a username and password (using the respective names "username" and "password") and then the following Passport definition:
passport.use(new localStrategy(
{usernameField: 'email'},
function(req, email, password, done) {
models.TeacherX.findOne({ email: email }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));
Then I call it with:
router.post('/login',
passport.authenticate('local', { failureRedirect: '/login?msg=failure'}),
function(req, res) {
res.redirect("/?msg=positive");
});
Since you are passing the request to the verify callback, you need to set the property passReqToCallback to true or remove the req param from the verify callback. Try this:
passport.use(new localStrategy(
{passReqToCallback: true ,
usernameField: 'email'},
function(req, email, password, done) {
models.TeacherX.findOne({ email: email }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));

Passport Authentication: chaining strategies

How do I run two or more passport strategies sequentially, meaning if one strategy comes up empty then run another one?
I tried doing this:
app.post('/', function (req,res,next){
passport.authenticate('strategy1', function (err, result1) {
if (err) { return next(err); }
if (!result1) {
passport.authenticate('strategy2', function (err,result2){
if (err) { return next(err); }
if(!result2){
return res.redirect('/');}
req.login(result2, function (err){
if(err){return next(err)}
res.render('result2');
})
});
}
req.login(result1, function (err){
if (err){return next(err)}
console.log('admin login found');
res.render('result');
});
})(req, res, next);
});
But am getting this error:
Error: Failed to serialize user into session
I have implemented:
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
I suspect the user was serialised in the first passport.authentication call and then it tried to serialise it again with the second one, and what I need to do is deserialise it again before running the second strategy.
Appreciate the help!
I ended up sticking to just one strategy but added logic to allow it to check several collections, as recommended in this answer.
passport.use('local', new LocalStrategy({
passReqToCallback : true
}, function(req, username, password, done) {
process.nextTick(function() {
collection1.findOne({'username': username}, function(err, collectionresult) {
if (err) {
return done(err);
}
if (!collectionresult) {
collection2.findOne({'username': username}, function(err, collection2result){
if (err) {
return done(err);
}
if (!collection2result) {
return done(null, false,req.flash('adminmessage','Invalid username or password'));
}
if (!collection2.validPassword(password)) {
return done(null, false,req.flash('adminmessage','Invalid username or password'));
}
console.log('local strategy has authenticated employee username and password! Returning employee');
return done(null, employee);
})
}
if (collection2result){
if (collection2result.password!=password) {
return done(null, false, req.flash('adminmessage','Invalid username or password' ));
}
else{
console.log('Local strategy has found an admin. Returning admin');
return done(null, collection2result)
}
}
});
}
);
}));

Credentials are not getting passed to authentication stategy

I'm new to node express and passport so I'm not sure what I'm missing. I'm trying to use a custom callback as all I want is a json response from my registration strategy. Currently I keep getting the following error "Missing credentials". I've been at it for a while now and I'm stuck.
Here is my controller:
app.post('/services/authentication/registration', function(req, res, next) {
console.log('before authentication')
passport.authenticate('local-registration', function(err, user, info) {
console.log('authentication callback');
if (err) { return res.send({'status':'err','message':err.message});}
if (!user) { return res.send({'status':'err','message':info.message});}
if (user!=false) { return res.send({'message':'registration successful'});}
})(req, res, next);
},
function(err, req, res, next) {
return res.send({'status':'err','message':err.message});
});
And my passport strategy:
passport.use('local-registration', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true
},
function(req, email, password, done) {
console.log('credentials passed to passport' + email + '' + password)
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// 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
User.findOne({ 'local.email' : email }, 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, {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.local.email = email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
I dont think you should send "req" as a parameter to the authentication function. take a look at this example from the passport docs:
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));

How to implements two strategies with passportjs

I have two model in my node/express app :
User model (simple user authenticated)
Manager model (user with different rights and different fields)
I want to use passport to authenticated them :
User with couple : Username / Password
Manager with couple : Email / Password
I correctly implemented PassportJS for the User model but now I try to do the same for Manager model.
exports.postlogin = function(req, res, next) {
passport.authenticate('user', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
req.session.messages = [info.message];
return res.redirect('/login')
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
};
exports.postlogin = function(req, res, next) {
passport.authenticate('manager', function(err, manager, info) {
if (err) { return next(err) }
if (!manager) {
req.session.messages = [info.message];
return res.redirect('/manager_signup')
}
req.logIn(manager, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
};
The two strategies :
passport.use('user', new LocalStrategy(function(username, password, done) {
UserModel.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
user.comparePassword(password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
passport.use('manager', new LocalStrategy({usernameField: 'manager_signin_email', passwordField: 'manager_signin_password'},function(manager_signin_email, manager_signin_password, done) {
ManagerModel.findOne({ email: manager_signin_email }, function(err, manager) {
if (err) { return done(err); }
if (!manager) { return done(null, false, { message: 'Unknown manager ' + manager_signin_email }); }
manager.comparePassword(manager_signin_password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
console.log('Manager login OK : ' + manager_signin_email);
return done(null, manager);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
The problem is for Serialize/Deserialize.
For User I have this :
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
UserModel.findById(id, function (err, user) {
done(err, user);
});
});
But I don't know how to do for Manager model.
You could maybe do something like this when serializing:
if (isUser(user)) {
// serialize user
done(null, "user_"+user.id );
} else if (isManager(user)) {
// serialize manager
done(null, "manager_"+user.id );
}
And then check the prefix when deserializing.
I think there is an open issue for what you want (https://github.com/jaredhanson/passport/issues/148) .
Alternatively you could change you serialize method to include information if it's user or manager not only user id and when deserializing read that info and load user/manager from proper model.
In case anyone is still stumbling upon this. You can check the type of the object you are serializing. Note all there of by objects are generalized in to a PrincipleInfo object.
function PrincipleInfo(principleId, principleType, details) {
this.principleId = principleId;
this.principleType = principleType;
this.details = details;
}
passport.serializeUser(function (userObjectThing, done) {
//userObjectThing could be a User or a Sponsor
var principleType = "user";
var userPrototype = Object.getPrototypeOf(userObjectThing);
if (userPrototype === User.prototype) {
principleType = "user";
} else if (userPrototype === Sponsor.prototype) {
principleType = "sponsor";
} else if (userPrototype === Admin.prototype) {
principleType = "admin";
}
var principleInfo = new PrincipleInfo(userObjectThing.id, principleType, '');
done(null,principleInfo);
});
passport.deserializeUser(function (principleInfo, done) {
if (principleInfo.principleType == 'user') {
User.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
} else if (principleInfo.principleType == 'sponsor') {
Sponsor.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
} else if (principleInfo.principleType == 'admin') {
Admin.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
}
});

Resources