Omid Ahourai
Omid Ahourai

Reputation: 1429

Express Passport (node.js) error handling

I've looked at how error handling should work in node via this question Error handling principles for Node.js + Express.js applications?, but I'm not sure what passport's doing when it fails authentication. I have the following LocalStrategy:

passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
  function(email, password, next) {
 
    User.find({email: UemOrUnm}, function(err, user){
      if (err) { console.log('Error > some err'); return next(err); }
      if (!user) { console.log('Error > no user'); return next('Incorrect login or password'); } 

      if (password != user.password) {
        return next(Incorrect login or password);
      }
      return next(null, user);
    });
  }
));

After I see 'Error > some err' console printout, nothing else happens. I would think it should continue on the the next path with an error parameter, but it doesn't seem to do that. What's going on?

Upvotes: 78

Views: 88905

Answers (6)

Niraj Dhungana
Niraj Dhungana

Reputation: 76

This is what I got after console.log(req) at the failler route.

const localStrategy = new LocalStrategy({ usernameField: "email" }, verifyUser);

passport.use(localStrategy);

const authenticateWithCredentials = passport.authenticate("local", {
  failureRedirect: "/api/auth/login-fail",
  failureMessage: true,
});

validation method find your user from db and throw error to the cb if there is any

const verifyUser = async (email, password, cb) => {
  const user = await User.findOne({ email });
  if (!user) return cb(null, false, { message: "email/password incorrect!" });

  const isMatched = await user.comparePassword(password);
  if (!isMatched)
    return cb(null, false, { message: "email/password incorrect!" });

  cb(null, {
    id: user._id,
    email,
    name: user.name,
  });
};

now setup your route

router.post("/sign-in", authenticateWithCredentials,(req, res) => {
  res.json({user: req.user})
});

router.get("/login-fail", (req, res) => {
  let message = "Invalid login request!";

  // if you are using typescript cast the sessionStore to any 
  const sessions = req.sessionStore.sessions || {};
  for (let key in sessions) {
    const messages = JSON.parse(sessions[key])?.messages;
    if (messages.length) {
      message = messages[0];
      break;
    }
  }

  res.status(401).json({ error: message });
});

Upvotes: 0

ysong4
ysong4

Reputation: 191

I found this thread very useful!

https://github.com/jaredhanson/passport-local/issues/2

You could use this to return error and render it in form.

app.post('/login',
  passport.authenticate('local', { successRedirect: '/home', failWithError: true }),
  function(err, req, res, next) {
    // handle error
    return res.render('login-form');
  }
);

Upvotes: 0

mialdi98
mialdi98

Reputation: 73

Some time has passed and now the most right code will be:

  passport.authenticate('local', (err, user, info) => {
    if (err) {
      return next(err); // will generate a 500 error
    }
    // Generate a JSON response reflecting authentication status
    if (!user) {
      return res.status(401).send({ error: 'Authentication failed' });
    }
    req.login(user, (err) => {
      if (err) {
        return next(err);
      }
      return res.status(202).send({ error: 'Authentication succeeded' });    
    });
});

Upvotes: 0

cpoole
cpoole

Reputation: 346

What Christian was saying was you need to add the function

req.login(user, function(err){
  if(err){
    return next(err);
  }
  return res.send({success:true});
});

So the whole route would be:

app.post('/login', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) {
      return next(err); // will generate a 500 error
    }
    // Generate a JSON response reflecting authentication status
    if (! user) {
      return res.send(401,{ success : false, message : 'authentication failed' });
    }
    req.login(user, function(err){
      if(err){
        return next(err);
      }
      return res.send({ success : true, message : 'authentication succeeded' });        
    });
  })(req, res, next);
});

source: http://passportjs.org/guide/login/

Upvotes: 22

robertklep
robertklep

Reputation: 203231

The strategy-implementation works in conjunction with passport.authenticate to both authenticate a request, and handle success/failure.

Say you're using this route (which is passed an e-mail address and a password):

app.post('/login', passport.authenticate('local', {
  successRedirect: '/loggedin',
  failureRedirect: '/login', // see text
  failureFlash: true // optional, see text as well
});

This will call the code in the strategy, where one of three conditions can happen:

  1. An internal error occurred trying to fetch the users' information (say the database connection is gone); this error would be passed on: next(err); this will be handled by Express and generate an HTTP 500 response;
  2. The provided credentials are invalid (there is no user with the supplied e-mail address, or the password is a mismatch); in that case, you don't generate an error, but you pass a false as the user object: next(null, false); this will trigger the failureRedirect (if you don't define one, a HTTP 401 Unauthorized response will be generated);
  3. Everything checks out, you have a valid user object, so you pass it along: next(null, user); this will trigger the successRedirect;

In case of an invalid authentication (but not an internal error), you can pass an extra message along with the callback:

next(null, false, { message : 'invalid e-mail address or password' });

If you have used failureFlash and installed the connect-flash middleware, the supplied message is stored in the session and can be accessed easily to, for example, be used in a template.

EDIT: it's also possible to completely handle the result of the authentication process yourself (instead of Passport sending a redirect or 401):

app.post('/login', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) {
      return next(err); // will generate a 500 error
    }
    // Generate a JSON response reflecting authentication status
    if (! user) {
      return res.send({ success : false, message : 'authentication failed' });
    }
    // ***********************************************************************
    // "Note that when using a custom callback, it becomes the application's
    // responsibility to establish a session (by calling req.login()) and send
    // a response."
    // Source: http://passportjs.org/docs
    // ***********************************************************************
    req.login(user, loginErr => {
      if (loginErr) {
        return next(loginErr);
      }
      return res.send({ success : true, message : 'authentication succeeded' });
    });      
  })(req, res, next);
});

Upvotes: 181

Christian Nagorka
Christian Nagorka

Reputation: 29

You need to add req.logIn(function (err) { }); and do the success redirect inside the callback function

Upvotes: 2

Related Questions