If you want to propagate an authentication failure (username and password mismatch), you should not generate an error, but set user to false and skip the reason:
passport.use(new LocalStrategy( {usernameField: 'email', passwordField: 'password'}, function(email, password, done) { if (canLogin) done(null, user); else done(null, false, { message: 'Invalid login credentials' }); } )); ... app.post('/login', function(req, res, next) { passport.authenticate('local', function(err, user, info) { if (user === false) {
err reserved for exceptions that occur during the authentication process, for example, if you receive DB errors, etc. But although Passport docs suggest that these errors will be passed to the passport.authenticate callback, they don't seem to (for whatever reason, it doesn't work for you).
source share