Rajat Bansal Rajat Bansal - 2 months ago 8
Node.js Question

Collision with local strategies passport in two seperate files

I have two models user and admin. They both have their own route files users.js and admins.js and their own local strategies. Users uses email for authentication whereas admin uses username. So they both have their own "passport.use" however, when using post '/login' for users, its somehow calling the "passport.use" of admin's. I dont know why.
Here is the users.js code:-

var express = require('express');
var router = express.Router();
var bodyParser=require('body-parser');
var User=require('../models/user');
var passport=require('passport');
var localStrategy=require('passport-local').Strategy;

router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.send('User not found');
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.json(user);
});
})(req, res, next);
});

passport.serializeUser(function(user, done) {
done(null, user.id);
});

//for sessions
passport.deserializeUser(function(id, done) {
User.getUserById(id, function(err, user) {
done(err, user);
});
});

//this doesnt seem to work..doesnt call this one
passport.use(new localStrategy({usernameField:'email', passwordField:'password'},function(email,password,done){
User.getUserByUsername(email, function(err,user){
if(err) throw err;
if(!user){
return done(null,false,{message: 'User not found'});
}

User.comparePassword(password, user.password, function(err, isMatch){
if(err) return done(err);
if(isMatch){
return done(null, user);
}
else{
return done(null,false,{message: 'Password doesnt match our records'});
}
});
});

}));


And this is admins.js code:-

router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, admin, info) {
if (err) {
return next(err);
}
if (!admin) {
return res.send('Admin not found');
}
req.logIn(admin, function(err) {
if (err) { return next(err); }
return res.json(admin);
});
})(req, res, next);
});

//for sessions
passport.serializeUser(function(admin, done) {
done(null, admin.id);
});

//for sessions
passport.deserializeUser(function(id, done) {
Admin.getAdminById(id, function(err, admin) {
done(err, admin);
});
});
//this gets called even when im calling /users/login
passport.use(new localStrategy(function(username,password,done){
Admin.getAdminByUsername(username, function(err,admin){
if(err) throw err;
if(!admin){
return done(null,false,{message: 'Admin not found'});
}

Admin.comparePassword(password, admin.password, function(err, isMatch){
if(err) return done(err);
if(isMatch){
return done(null, admin);
}
else{
return done(null,false,{message: 'Password doesnt match our records'});
}
});
});

}));


Note that both these files are at the same level under routes folder..and their model files are in the model folder again at the same level. Admin works perfectly but User authentication isnt happening.

Answer

The problem is that passport.use() take as optional first parameter the logical name used to describe an strategy. Every strategy algorithm provide its own default name. And when you perform an authentication the passport framework select the strategy based on that name. Therefore in your case the last registration for the local name (the default name for the local strategy) you are fetching the last algorithm registered for that name.

A solution will be to perform the following modification on users.js code:

var User=require('../models/user');
var passport=require('passport');
var localStrategy=require('passport-local').Strategy;

router.post('/login', function(req, res, next) {
  passport.authenticate('local-users', function(err, user, info) {
    if (err) { 
      return next(err);
    }
    if (!user) {   
      return res.send('User not found'); 
    }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.json(user);
    });
  })(req, res, next);
});

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

//for sessions
passport.deserializeUser(function(id, done) {
  User.getUserById(id, function(err, user) {
    done(err, user);
  });
});

//this doesnt seem to work..doesnt call this one
passport.use('local-users', new localStrategy({usernameField:'email', passwordField:'password'},function(email,password,done){
    User.getUserByUsername(email, function(err,user){
      if(err) throw err;
      if(!user){
        return done(null,false,{message: 'User not found'});
      }

      User.comparePassword(password, user.password, function(err, isMatch){
        if(err) return done(err);
        if(isMatch){
          return done(null, user);
        }
        else{
          return done(null,false,{message: 'Password doesnt match our records'});
        }
      });
    });

  }));

As you can see we are providing an alternate strategy name for the LocalStrategy on the context of the user module. And we are using the strategy (on passport.authenticate call) using this specific strategy name.

Comments