Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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");
    }));
like image 828
Mael Jarnole Avatar asked Oct 16 '14 12:10

Mael Jarnole


People also ask

How do I use flash on my Passport?

Note: Using flash messages requires a req. Use of connect-flash middleware is recommended to provide this functionality when using Express 3. x. So you need to install connect-flash express middleware as it is recommended. var flash = require('connect-flash'); var app = express(); app.

Should I use Passport js for authentication?

Passport is a popular, modular authentication middleware for Node. js applications. With it, authentication can be easily integrated into any Node- and Express-based app. The Passport library provides more than 500 authentication mechanisms, including OAuth, JWT, and simple username and password based authentication.

What does Passport authenticate () do?

In this route, passport. authenticate() is middleware which will authenticate the request. By default, when authentication succeeds, the req. user property is set to the authenticated user, a login session is established, and the next function in the stack is called.


2 Answers

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.

like image 90
vesse Avatar answered Oct 07 '22 15:10

vesse


It's an old question, but I had trouble finding an answer. Hopefully this helps others.


I think the documentation is a little incomplete when it comes to using connect-flash. They say:

Note: Using flash messages requires a req.flash() function. Express 2.x provided this functionality, however it was removed from Express 3.x. Use of connect-flash middleware is recommended to provide this functionality when using Express 3.x.

Yet, there's no mention of using req.flash in the done() callback. Based on the scotch.io tutorial, you actually should call req.flash() right there in the callback. It works for me.

// In your strategy
...
if (user) {
    return done( null, false, req.flash('loginMessage','Pas d\'utilisateur avec ce login.') );
...

You will need to use passReqToCallback of course. Also be sure failureFlash is set to true. OP is already doing these correctly.

Now you can check the flash message in the route. Note that connect-flash sends an array of messages. That could be OP's problem, if his template is expecting a string.

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

    // Test flash messages in the console
    console.log( req.flash('loginMessage') ); // This returns an array
    console.log( req.flash('loginMessage')[0] ); // This returns a string

    // render the page and pass in any flash data if it exists
    res.render('login', {
        title: "Connexion",
        message: req.flash('loginMessage')[0] // Don't forget the index! 
    });

});

If there's a chance of having multiple login messages on a page, pass the whole req.flash('loginMessage') array and iterate through it in your template. Below is an example using nunjucks.


Protip:

If you have many routes with flash messages, you can always set them to res.locals in a middleware route. This will not interfere with other locals, like title. Here is my implementation, using bootstrap alerts.

In my strategy:

...
if (!user){
    return done( null, false, req.flash('danger','No account exists for that email.') );
}
...

In my routes.js:

// Set flash messages
router.get('*', function(req,res,next){
    res.locals.successes = req.flash('success');
    res.locals.dangers = req.flash('danger');
    res.locals.warnings = req.flash('warning');
    next();
});

// Login route
app.get('/login', function(req, res) {
    res.render('login', { title: 'Login'}); 
});

In my nunjucks base template:

<!--Messages-->
{% for danger in dangers %}
    <div class='header alert alert-danger alert-dismissible'>
        <strong><i class="fa fa-exclamation-circle"></i> ERROR:</strong> {{ danger | safe }}
        <a href="#" class='close' data-dismiss="alert" aria-label="close"><i class='fa fa-times'></i></a> 
    </div>
{% endfor %}
{% for warning in warnings %}
    <div class='header alert alert-warning alert-dismissible'>
        <strong><i class="fa fa-check-circle"></i> Warning:</strong> {{ warning | safe }}
        <a href="#" class='close' data-dismiss="alert" aria-label="close"><i class='fa fa-times'></i></a> 
    </div>
{% endfor %}
{% for success in successes %}
    <div class='header alert alert-success alert-dismissible'>
        <strong><i class="fa fa-check-circle"></i> Success!</strong> {{ success | safe }}
        <a href="#" class='close' data-dismiss="alert" aria-label="close"><i class='fa fa-times'></i></a> 
    </div>
{% endfor %}
like image 43
Keith Avatar answered Oct 07 '22 13:10

Keith