Node.js Authentication with Passport: How to flash a message if a field is missing?

Mael Jarnole picture Mael Jarnole · Oct 16, 2014 · Viewed 41k times · Source

I am using passport.js and I'd like to flash a message if the fields of my form are empty. But I don't know how to do it since passport doesn't trigger the strategy callback if those are missing. I really want this use case to be more clear, and I don't want to modify passport. I feel like there is a way to do so but I don't know where! I've tried to use the callback of the route (app.post) but it doesn't seem to work the way I tried.

Here is the authenticate function prototype:

Strategy.prototype.authenticate = function(req, options) {
  options = options || {};
  var username = lookup(req.body, this._usernameField) || lookup(req.query, this._usernameField);
  var password = lookup(req.body, this._passwordField) || lookup(req.query, this._passwordField);
  // here is my problem
  if (!username || !password) {
    return this.fail({ message: options.badRequestMessage || 'Missing credentials' }, 400);
  }

  var self = this;

  function verified(err, user, info) {
    if (err) { return self.error(err); }
    if (!user) { return self.fail(info); }
    self.success(user, info);
  }

  try {
    if (self._passReqToCallback) {
      this._verify(req, username, password, verified);
    } else {
      this._verify(username, password, verified);
    }
  } catch (ex) {
    return self.error(ex);
  }
};

Here is my strategy:

 passport.use('local-login', new LocalStrategy({
        usernameField : 'email',
        passwordField : 'password',
        passReqToCallback : true 
    },
    function(req, email, password, done) { 
        // ...
        console.log("Hello");
        User.findOne({ 'local.email' :  email }, function(err, user) {
            if (err)
                return done(err);

            // if no user is found, return the message
            if (!user)
                return done(null, false, req.flash('loginMessage', 'Pas d\'utilisateur avec ce login.')); // req.flash is the way to set flashdata using connect-flash

            // if the user is found but the password is wrong
            if (!user.validPassword(password))
                return done(null, false, req.flash('loginMessage', 'Oops! Mauvais password.')); // create the loginMessage and save it to session as flashdata

            // all is well, return successful user
            return done(null, user);
        });

    }));

And finally my route:

app.get('/login', function(req, res) {

    // render the page and pass in any flash data if it exists
    res.render('login', { title: "Connexion", message: req.flash('loginMessage') }); 
});

// process the login form
    app.post('/login', passport.authenticate('local-login', {
        successRedirect : '/profile', // redirect to the secure profile section
        failureRedirect : '/login', // redirect back to the signup page if there is an error
        failureFlash : true // allow flash messages
    }, function(err, user, info) {
         // Was trying this callback, does'nt work, post callback maybe ?
         console.log("Hello");
    }));

Answer

vesse picture vesse · Oct 16, 2014

You should not call req.flash in your verify callback. Instead you should return a message as shown in the documentation. Passport will put the message returned to flash message when failureFlash: true:

Setting the failureFlash option to true instructs Passport to flash an error message using the message given by the strategy's verify callback, if any.

Your revised verify callback:

passport.use('local-login', new LocalStrategy({...},
  function(email, password, done) { 
    User.findOne({ 'local.email' :  email }, function(err, user) {
      if (err)
        return done(err);
      if (!user)
        return done(null, false, {message: 'Pas d\'utilisateur avec ce login.'});
      if (!user.validPassword(password))
        return done(null, false, {message: 'Oops! Mauvais password.'});
      return done(null, user);
    });
  }));

And routes:

app.get('/login', function(req, res) {
  console.log(req.flash('error'));
  res.send();
});

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

Edit:

Here's a fully working example: https://gist.github.com/vesse/9e23ff1810089bed4426

Edit:

This does not indeed answer the original question which was I am using passport.js and I'd like to flash a message if the fields of my form are empty. passport-local strategy does just execute fail if the form fields are empty, so they should be checked before the authentication middleware and set the flash message outside passport.