Search code examples
mongoosepassport.jspassport-local

Unable to catch a 401 condition using Passport login


I am using PassportJS with Local Strategy and passport-local-mongoose. Here's my login script:

// Configure Passport (server.js)
// ---------------------------------------------------------------
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// ---------------------------------------------------------------

.

// POST to /login (authenticate.js)
// ---------------------------------------------------------------
router.post('/login', (req, res) => {
    // server-side validation
  const errors = {
    username: Validator.validateusername(req.body.username),
    password: Validator.validatepassword(req.body.password),
  };
  if (!isEmpty(errors)) return res.send(JSON.stringify({ error: errors }));

  passport.authenticate('local')(req, res, () => {
    // If logged in, we should have user info to send back
    if (req.user) {
      const userdata = JSON.stringify(req.user);
      const token = jwt.sign({
        username: req.user.username,
        firstName: req.user.firstName,
        lastName: req.user.lastName,
        email: req.user.email,
        img: req.user.img,
      }, process.env.JWT_SECRET);
      res.cookie('token', token);
      return res.send(userdata);
    }
    // Otherwise return an error
    return res.send(JSON.stringify({ error: 'There was an error logging in' }));
  });
});

This works fine except when there's a login error. If the login fails for whatever reason (401 or 500), shouldn't this script return the There was an error logging in message? Instead, it just returns a 401 Unauthorized?

The schema this authentication looks up is:

const { mongoose } = require('../config/dbconfig');

const Schema = mongoose.Schema;
const passportLocalMongoose = require('passport-local-mongoose');

const User = new Schema({
  username: {
        type: String,
        lowercase: true,
        required: true,
        unique: true,
    },
  password: {
        type: String,
        select: false,
        required: true,
    },
  firstName: {
        type: String,
        required: true,
    },
  lastName: {
        type: String,
        required: true,
    },
  email: {
        type: String,
        lowercase: true,
        required: true,
        unique: true,
    },
  img: {
        type: String,
    },
}, { timestamps: true });

User.plugin(passportLocalMongoose);

module.exports = mongoose.model('User', User);

Solution

  • You are calling the strategy a little wrong. You should use it as a middleware, or use the custom callback way. Yours is a kind of mix between these - you call the strategy like it was a middleware but provide your own next middleware function. Since authenticate is called as middleware but no failureRedirect option is provided, Passport will return 401 by default. The next function is called when successfully authenticated, and in your case it is not the next middleware but a callback function.

    To use the custom callback you should write the route handler like this:

    app.post('/login', (req, res, next) => {
      // ...
      passport.authenticate('local', (err, user, info) => {
        if (err) { return next(err); }
        if (user) {
          // ...
          req.login(user, (err) => {
            if (err) { return next(err); }
            return res.send(userdata);
          });
        } else {
          return res.status(401).send({ error: 'There was an error logging in' });
        }
      })(req, res, next);
    });