How to implements two strategies with passportjs - node.js

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

Related

Can I deserialize the different users like student and admin in passport.js?

In passport js authentication when the deserialize the user I have the following doubt?
Can I use different Login tables to fetch the user ?(depends on they are admin or student like that)
But I was try to implement it by using if statement that was won't work it which mean cannot fetch from db. What I am doing that any help !
Using LocalStrategy u can use it. Here you have to make two different calls to check.
Sample:
passport.use(new LocalStrategy(
function(username, password, done) {
async.parallel([
function(cb){
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return cb(null, user);
});
},
function(cb){
Admin.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return cb(null, user);
});
}
], ([res1, res2]) => {
// on result
done(null, res1)
// OR
done(null, res1)
})
}
));

how to send json data under passport local strategy

This is my passport Login Handler.
Now I want to send JSON Data under every condition so that API can access the response and on behalf of this display anything on frontend.
//Login Handler
passport.use('local-user', new LocalStrategy(
function(username, password, done) {
User.getUserByUsername(username, function(err, user){
if(err) {
console.log('error')
logger.log('error', 'Error Generates on User.getUserByUsername Query on users.js file');
throw err;
}
if(!user){
//res.send('unknown user');
console.log('Unknown User');
return done(null, false, {message: 'Unknown User'});
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) {
logger.log('error', 'Error Generates on User.comparePassword Query on users.js file');
throw err;
}
if(isMatch){
return done(null, user);
}else{
return done(null, false, {message: 'Invalid Credential, please check carefully...!'})
}
});
});
}
));
Anyone have any idea for this? Thanks in advance
The local strategy will pass the user or error with done(), then you receive that with a callback and pack it with res.json()
Here is my implementation. May help?
passport.use(
new LocalStrategy(
{
usernameField: "email"
},
function(username, password, done) {
User.findOne({ email: username }, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
email: "Email not found"
});
}
if (!user.validPassword(password)) {
return done(null, false, {
password: "Password is wrong"
});
}
return done(null, user);
});
}
)
);
router.post("/login", function(req, res) {
passport.authenticate("local", function(err, user, info) {
if (err) {
res.status(404).json(err);
return;
}
if (user) {
const token = user.generateJwt();
res.status(200);
res.json({
userInfo: user,
token: token
});
} else {
res.status(401).json(info);
}
})(req, res);
});

Serialize different types of users in PassportJS

I have two local strategies for two different types of users: Students and Teachers. I am having trouble trying to login because my serialization is not working properly.
I have two models, Student and Teacher. Both have their separate collections, student and teacher with similar schema. Both have similar functions:
module.exports.getStudentByUsername = function(username, callback){
var query = {username: username};
User.findOne(query, callback);
}
module.exports.getStudentById = function(id, callback){
User.findById(id, callback);
}
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
callback(null, isMatch);
});
}
My two local strategies are:
passport.use('student-local', new LocalStrategy(
function(username, password, done) {
Student.getStudentByUsername(username, function(err, student){
if(err) throw err;
if(!student){
return done(null, false, {message: 'Unknown Student'});
}
Student.comparePassword(password, student.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, student);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
passport.use('teacher-local', new LocalStrategy(
function(username, password, done) {
Teacher.getTeacherByUsername(username, function(err, teacher){
if(err) throw err;
if(!teacher){
return done(null, false, {message: 'Unknown Teacher'});
}
Teacher.comparePassword(password, teacher.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, teacher);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
My two logins are:
router.post('/loginStudent',
passport.authenticate('student-local', {successRedirect:'/users/student', failureRedirect:'/login',failureFlash: true}),
function(req, res) {
res.redirect('/');
});
router.post('/loginTeacher',
passport.authenticate('teacher-local', {successRedirect:'/users/teacher', failureRedirect:'/login',failureFlash: true}),
function(req, res) {
res.redirect('/');
});
My serialization and deserialization are:
passport.serializeUser(function(user, done) {
if(Student.findOne({username: user.username}).length != 0) {
done(null, user.id);
} else if(Teacher.findOne({username: user.username}).length != 0) {
done(null, user.id);
}
});
passport.deserializeUser(function(id, done) {
if(Student.getStudentById(id)){
Student.getTeacherById(id, function(err, user) {
done(err, user);
});
} else {
Teacher.getTeacherById(id, function(err, user) {
done(err, user);
});
}
});
I know my serialization is wrong and that is why I am not being able to get this to work. When I tried with only one type of user and with the simpler serialization I found in the passport documents, my login was working fine.I am new with node and passport so any help would be highly appreciated.
I believe your serialization doesn't work, because the query for finding the teacher / student is actually async and you are treating it as sync.
passport.serializeUser(function(user, done) {
Student.findOne( { username: user.username }, function( err, student ) {
if ( student ) {
// user is student
done( null, user.id );
} else {
Teacher.findOne( { username: user.username }, function( err, teacher ) {
if ( teacher ) {
// user is teacher
done( null, user.id );
}
} );
}
} )
} );
This should work, although I'm not sure why you are doing to lookup for the type of user. You can just do :
passport.serializeUser(function(user, done) {
done(null, user.id);
} );

Passport local authentication is not working

I am building app using MEAN STACK. I want to use passport-local authentication for my login form. But a the time of form submission i am getting POST http://localhost/login 404 (Not Found) please have a look of my code below This is my controller:
lyfee.controller('loginCtrl', ['$scope', '$http', function($scope, $http) {
$scope.user = {};
$scope.login = function() {
// var data = {User: $scope.user }
//console.log($scope.user);
console.log("login function call");
$http.post('/login', $scope.user);
console.log("login request send");
}
}]);
and this is my server.js :
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.getUserByusername(username, function(err, user) {
if (err) throw err;
if (!user) {
return done(null, false, {
message: 'Unknown USER'
});
}
User.comparePassword(password, user.password, function(err, isMatch) {
if () throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, {
message: 'Invalid password'
});
}
});
});
}));
app.post('/login',
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}),
function(req, res) {
res.redirect('/');
});
in which file should i write getUserByusername and comparePassword function ? and what is mistake i am doing please correct it and give me some suggestion.
In your model suppose that User.js write functions like this:
/**
* Check the user's password
*/
dbSchema.methods.comparePassword = function(candidatePassword, cb) {
var status = this.password.localeCompare(candidatePassword.trim());
if (status != 0) {
return cb(err);
}
cb(null, true);
};
then use the function like this
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({
username: username
}, function(err, user) {
if (err) throw err;
if (!user) {
return done(null, false, {
message: 'Unknown USER'
});
}
/**
* Check the user's password
*/
User.comparePassword(password, user.password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, {
message: 'Invalid password'
});
}
});
});
}));

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

Resources