Search code examples
javascriptnode.jsauthenticationpassport.jspassport-local

How to login users with passport-local and display an error message?


So I've been trying to write an authentication system on my VueJs app with PassportJs. I wrote a function to initialize the basics of PassportJs that looks like this:

module.exports = {
  passport: function () {
    authenticateUser = async (email, password, done) => {
      User.findOne({ email: email }, async (err, user) => {
        if (user == null) return done(null, false, { msg: 'No user registered with this email' })

        try {
          if (await bcrypt.compare(password, user.password)) {
            return done(null, user)
          } else {
            return done(null, false, { msg: "Password incorrect" })
          }
        } catch (e) {
          return done(e)
        }
      })
    }

    passport.use(new LocalStrategy({ usernameField: 'email', }, authenticateUser));
    passport.serializeUser((user, done) => {
      console.log('passport serializeUser')
      done(null, user.id)
    })

    passport.deserializeUser((id, done) => {
      console.log('passport deserializeUser')
      User.findById(id, (err, user) => {
        done(err, user)
      })
    })
  },
};

Now, literally the only thing that I need is to display the proper error messages on the front end.

This code works for displaying the error messages, but does NOT work for actually logging in the users:

router.post('/login', function (req, res, next) {
  passport.authenticate('local', function (err, user, info) {
    if (err) { return next(err); }
    if (!user) {
      console.log('mand?????: ', info)
      res.status(401).json({ msg: info.msg });
      return;
    }
  })(req, res, next);
});

I can see that this code gets run (await bcrypt.compare(password, user.password)) but it does not serialize the user. The request just keep 'pending' but does not finish.

This code works for actually logging in the user, but does NOT work for displaying the error messages:

router.post('/login', passport.authenticate('local'))

Where am I going wrong? How can I login the user succesfully, but also display the proper error messages as declared int he done() function from passportjs?


Solution

  • Take a look at passport documentation for the authenticate() method.

    When using a custom callback, it becomes the application's responsibility to establish a session (by calling req.login()) and send a response.

    So in your first scenario, you need to explictly establish a session - passport will not do it automatically for you.

    Regarding your second scenario where no custom callback is used:

    By default, if authentication fails, Passport will respond with a 401 Unauthorized status, and any additional route handlers will not be invoked.

    As far as I understand, with this setup, flash messages set up as part of the Strategy verify callback are not being used - unless redirect options are specified, which you do not want here.

    So you probably should go with the first scenario in order to be able to customize error responses with the Strategy verify callback flash messages. Just make sure you establish the session when login is successful.

    http://www.passportjs.org/docs/authenticate/

    Edit: I've just ran through a quick test, and it seems that alternatively you should be able to get away with just having the Strategy verify callback return errors with the error.message property set to whatever error message you want to send as a response - without the need for the custom callback and flash messages at all. Note though that in this case passport actually sets the response body to the error message as text.

    So for instance instead of:

    done(null, false, { msg: 'No user registered with this email' })
    

    You would do:

    done(new Error('No user registered with this email')