web-dev-qa-db-fra.com

Node.js Authentication with Passport: Comment flasher un message si un champ est manquant?

J'utilise passport.js et j'aimerais envoyer un message flash si les champs de mon formulaire sont vides. Mais je ne sais pas comment le faire car passeport ne déclenche pas le rappel de la stratégie si ceux-ci manquent. Je souhaite vraiment que ce cas d'utilisation soit plus clair et je ne souhaite pas modifier le passeport. J'ai l'impression qu'il y a un moyen de le faire mais je ne sais pas où! J'ai essayé d'utiliser le rappel de la route (app.post), mais cela ne semble pas fonctionner comme j'ai essayé.

Voici le prototype de la fonction d'authentification:

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);
  }
};

Voici ma stratégie:  

 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);
        });

    }));

Et enfin mon itinéraire:

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");
    }));
11
Mael Jarnole

Vous ne devez pas appeler req.flash dans votre rappel de vérification. À la place, vous devriez renvoyer un message comme indiqué dans la documentation . Passport mettra le message renvoyé au message flash lorsque failureFlash: true.

Votre rappel de vérification révisé:

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);
        });

    }));

Et des itinéraires:

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
}));

Modifier: 

Voici un exemple pleinement fonctionnel: https://Gist.github.com/vesse/9e23ff1810089bed4426

16
vesse

C'est une vieille question, mais j'ai eu du mal à trouver une réponse. Espérons que cela aide les autres. 


Je pense que la documentation est un peu incomplet en ce qui concerne l'utilisation de connect-flash. Ils disent: 

Remarque: L'utilisation de messages flash nécessite une fonction req.flash (). Express 2.x fournit cette fonctionnalité, mais elle a été supprimée d’Express 3.x. L'utilisation du middleware connect-flash est recommandée pour fournir cette fonctionnalité lors de l'utilisation d'Express 3.x.

Pourtant, il n'est pas question d'utiliser req.flash dans le rappel done(). D'après le tutoriel scotch.io }, vous devez en fait appeler [] req.flash() à cet endroit du rappel. Ça marche pour moi. 

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

Vous devrez utiliser passReqToCallback bien sûr. Assurez-vous également que failureFlash est défini sur true. OP les fait déjà correctement. 

Maintenant, vous pouvez vérifier le message flash dans l'itinéraire. Notez que connect-flash envoie un tableau de messages. Cela pourrait être un problème pour OP, si son modèle attend une chaîne. 

// 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! 
    });

});

S'il y a une chance que plusieurs messages de connexion soient présents sur une page, transmettez l'ensemble du tableau req.flash('loginMessage') et parcourez-le dans votre modèle. Ci-dessous, un exemple utilisant nunjucks


Protip:

Si vous avez plusieurs itinéraires avec des messages flash, vous pouvez toujours les définir sur res.locals dans un itinéraire de logiciel intermédiaire. Cela n'interférera pas avec les autres sections locales, comme title. Voici mon implémentation, en utilisant alertes d'amorçage

Dans ma stratégie: 

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

Dans mes 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'}); 
});

Dans mon modèle de base nunjucks:

<!--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 %}
5
Keith

Vous devez définir badRequestMessage et failureFlash: true

Comme ça:

passport.authenticate('login', {
    successRedirect : '/',
    failureRedirect : '/login',
    badRequestMessage : 'Missing username or password.',
    failureFlash: true
})
4
MrR0FL0LMA0MG

Après avoir passé des mois à essayer de faire fonctionner le flash en panne, j'ai finalement trouvé une solution qui n'utilise pas la fonctionnalité failureFlash. J'ai essentiellement créé un nouvel itinéraire et envoyé le message flash.

app.post('/login',
  passport.authenticate('local', {failureRedirect: "/loginfailed"}),
  function(req, res) {
    if (!req.user.isActive){
      req.flash("success","Your account needs to be verified. Please check your email to verify your account");
      req.logout();
      res.redirect("back")
    }else{
      res.redirect("/");
    }
  });

  //Route to login page if user failed to login. I created this to allow flash messages and not interfere with regular login route
  app.get("/loginfailed", function(req, res){
    if (!req.user){
      req.flash("success", "Username or password is incorrect.");
      res.redirect("/login");
    }
  });
2
Joel

Lorsque les champs requis pour l'authentification sont manquants, passport.authenticatene déclenchera pas le rappel de stratégie, comme le souligne l'OP.
Cela doit être géré dans le custom callback (page de défilement vers le bas) dans la fonction d’authentification en utilisant le paramètre info.
Dans le cas du code de l'OP comme ceci: 

app.post('/login', function (req, res, next) { 
    passport.authenticate('local-login',
    {
      successRedirect: '/profile',
      failureRedirect: '/login',
      failureFlash: true,
    },
    function (error, user, info) {
      //This will print: 'Missing credentials'
      console.log(info.message);
      //So then OP could do something like 
      req.flash(info.message);
      //or in my case I didn't use flash and just did 
      if (info)
        res.status(400).send(info.message);
      ...
    })(req, res, next);
  });

Je sais que cette question est ancienne mais je suis tombé sur cette question moi-même et je constate qu'il n'y a toujours pas de réponse acceptée. De plus, je pense que toutes les réponses ont mal interprété ce que le PO demandait réellement: un moyen d'accéder à badRequestMessage.
Les documents PassportJS ne sont pas très utiles non plus:

Si l'authentification échoue, l'utilisateur sera défini sur false. Si une exception survient, err sera défini. Un argument d'information facultatif sera passé, contenant des détails supplémentaires fournis par le rappel de vérification de la stratégie.

Cela signifie en réalité que le paramètre info peut être passé en tant que troisième paramètre de votre stratégie, comme suit: done(error,user,info), mais ne mentionne pas que ce paramètre est utilisé par défaut en cas d'informations manquantes. Globalement, je pense que les documents de PassportJS pourraient faire l’objet d’une refonte car ils manquent de détails et/ ils renvoient à des exemples non existants .

Cette réponse m'a aidé à comprendre que le message d'informations d'identification manquantes est transmis dans le paramètre info.

1
Dawid Stróżak