14
votes

As per the documentation, if I was handling authentication requests like so, I would be able to capture successful attempts.

app.post('/login',
  passport.authenticate('local'),
  function(req, res) {
    // If this function gets called, authentication was successful.
    // `req.user` contains the authenticated user.
    res.redirect('/users/' + req.user.username);
  });

But, like the documentation says:

By default, if authentication fails, Passport will respond with a 401 Unauthorized status, and any additional route handlers will not be invoked. If authentication succeeds, the next handler will be invoked and the req.user property will be set to the authenticated user.

How can I handle the unauthorized login attempt?

I know I can handle it with custom middleware but is there a better way?

2

2 Answers

22
votes

You should have a look at the Custom Callback section in passport docs which explains about how to override the built in behavior of handling an authentication request. You can write a custom callback which will serve as the done function that you are invoking from the Strategy.

app.get('/login', function(req, res, next) {
  /* look at the 2nd parameter to the below call */
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect('/login'); }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/users/' + user.username);
    });
  })(req, res, next);
});

Look at the second parameter to the passport.authenticate call, which will serve as the done function that you invoke from the local strategy.

See the done function invoked in the code below, which is the local strategy that you define for passport. You can call the done function with various available parameters like err, user, info set from the strategy according to the response from API call or db operation. These parameters will be processed by the above function definition in the passport.authenticate call.

passport.use(new LocalStrategy(
  function(username, password, done) {
    /* see done being invoked with different paramters
       according to different situations */
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      if (!user.verifyPassword(password)) { return done(null, false); }
      return done(null, user);
    });
  }
));
6
votes

In my case I didn't want the 401 error (which is the "default behavior"), and I just wanted a simple redirect if the user wasn't authorized. The simple { failureRedirect: '/login' } worked for me, as explained on the PassportJS documentation (in the "Overview" and the "Redirects" header)

Despite the complexities involved in authentication, code does not have to be complicated.

app.post('/login', passport.authenticate('local', { successRedirect:'/',
                                                    failureRedirect: '/login' }));

In this case, the redirect options override the default behavior. Upon successful authentication, the user will be redirected to the home page. If authentication fails, the user will be redirected back to the login page for another attempt.

The accepted answer gives you more flexibility. But this answer is a bit simpler.

I'm pretty sure "failure" includes the case where there would be no user: if (!user), but I'm not sure if failure includes the error case or not: if (err), as seen in the accepted answer.