简体   繁体   English

使用 Passport 进行 Node.js 身份验证:如果缺少字段,如何闪烁消息?

[英]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.我正在使用passport.js,如果我的表单字段为空,我想显示一条消息。 But I don't know how to do it since passport doesn't trigger the strategy callback if those are missing.但我不知道该怎么做,因为如果缺少这些,passport 不会触发策略回调。 I really want this use case to be more clear, and I don't want to modify passport.我真的希望这个用例更清楚,我不想修改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.我尝试使用路由的回调( app.post ),但它似乎并没有像我尝试的那样工作。

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

You should not call req.flash in your verify callback.您不应在验证回调中调用req.flash Instead you should return a message as shown in the documentation .相反,您应该返回一条消息,如文档中所示 Passport will put the message returned to flash message when failureFlash: true :failureFlash: true时,Passport 会将消息返回给 flash 消息:

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.failureFlash选项设置为true指示 Passport 使用策略的 verify 回调(如果有)给出的消息闪烁错误消息。

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这是一个完整的示例: https : //gist.github.com/vessel/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.js,如果我的表单字段为空,我想闪现一条消息 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.如果表单字段为空, passport-local策略只会执行fail ,因此应该在身份验证中间件之前检查它们并将 flash 消息设置在护照之外。

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 .我认为文档在使用connect-flash时有点不完整。 They say:他们说:

Note: Using flash messages requires a req.flash() function.注意:使用 flash 消息需要 req.flash() 函数。 Express 2.x provided this functionality, however it was removed from Express 3.x. Express 2.x 提供了此功能,但它已从 Express 3.x 中删除。 Use of connect-flash middleware is recommended to provide this functionality when using Express 3.x.使用 Express 3.x 时,建议使用 connect-flash 中间件来提供此功能。

Yet, there's no mention of using req.flash in the done() callback.然而,没有提到在done()回调中使用 req.flash。 Based on the scotch.io tutorial , you actually should call req.flash() right there in the callback.根据scotch.io 教程,您实际上应该在回调中直接调用req.flash() 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.您当然需要使用passReqToCallback Also be sure failureFlash is set to true .还要确保failureFlash设置为true OP is already doing these correctly. OP 已经正确执行了这些操作。

Now you can check the flash message in the route.现在您可以检查路由中的 flash 消息。 Note that connect-flash sends an array of messages .请注意, connect-flash发送一组消息 That could be OP's problem, if his template is expecting a string.如果他的模板需要一个字符串,那可能是 OP 的问题。

// 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.如果页面上可能有多个登录消息,请传递整个req.flash('loginMessage')数组并在模板中遍历它。 Below is an example using nunjucks .下面是一个使用nunjucks的例子。


Protip:专家提示:

If you have many routes with flash messages, you can always set them to res.locals in a middleware route.如果你有很多带有 flash 消息的路由,你总是可以在中间件路由中将它们设置为res.locals This will not interfere with other locals, like title .这不会干扰其他本地人,例如title Here is my implementation, using bootstrap alerts .这是我的实现,使用bootstrap alerts

In my strategy:在我的策略中:

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

In my routes.js:在我的 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 %}

You need to set badRequestMessage and set failureFlash: true .您需要设置badRequestMessage并设置failureFlash: true

Like this:像这样:

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

After months of on and off trying to get failure flash to work, i finally found a solution which doesnt use the failureFlash feature.经过几个月的断断续续尝试使故障闪存工作,我终于找到了一个不使用故障闪存功能的解决方案。 I basically created a new route and sent the flash 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");
    }
  });

I had the same problem and I solved it.我有同样的问题,我解决了它。
Your success message and failure message variables have to match with whatever the passport JS is using.您的成功消息和失败消息变量必须与通行证 JS 使用的任何内容相匹配。 So after playing around, I realize that passport JS is using the variable success to display success flash and error to display failure flash.所以玩了一圈之后,我意识到passport JS使用变量success来显示success flash和error来显示fail flash。

So first, you can create a super global variable like this in your app.js:所以首先,你可以在 app.js 中创建一个像这样的超级全局变量:

app.use(function(req, res, next) {
    res.locals.error = req.flash("error");
    res.locals.success = req.flash("success");
    next();
});

Then use those variables in your temple.然后在您的寺庙中使用这些变量。 I am using ejs so it looks like this:我正在使用 ejs,所以它看起来像这样:

<%if(error && error.length > 0){%>
    <div class="alert alert-danger"><%=error%></div>
<%}%>
    <%if(success && success.length > 0){%>
    <div class="alert alert-success"><%=success%></div>
<%}%>

And finally your passport JS code should be like this:最后你的护照JS代码应该是这样的:

router.post("/login",passport.authenticate("local", {
    successFlash : "Hey, Welcome back",
    successRedirect : "/mountains",
    failureFlash : true,
    failureRedirect :"/login"
    }), function(req, res){
});

My solution我的解决方案

app.js code: app.js 代码:

const flash = require('connect-flash');
app.use(flash());
require('./src/config/passport.js')(app);

local.strategy.js code local.strategy.js 代码

const passport = require('passport');
const { Strategy } = require('passport-local');
const userModel = require('./../../../models/userModel');

module.exports = function localStrategy() {
passport.use(new Strategy(
    {
        usernameField: "username",
        passwordField: "password"
    }, (username, password, done) => {
        userModel.findOne({ username }, (err, user) => {
            if (err) {
                res.send(err);
            }
            if (user && (user.username == username && user.password == password)) {
                done(null, user, { message: "Success" });
            } else {
                done(null, false, { message: "Invalid credentials!" });
            }
        });
      }
       ));
  }

authController.js code authController.js 代码

function signIn(req, res) {
    res.render('signin', {
        nav,
        title: "Sign in",
        message: req.flash()
    });
};

authRouter.js code authRouter.js 代码

authRouter.route('/signin').get(signIn).post(passport.authenticate('local', {
    successRedirect: '/admin',
    failureRedirect: '/auth/signin',
    failureFlash: true
}));

signin.js template code (my view engine is ejs) signin.js 模板代码(我的视图引擎是 ejs)

<% if (message) { %>
  <p style="color: red;" class="text-center"><%= message.error %></p>
<% } %>

When fields required for authentication are missing, passport.authenticate will not trigger the Strategy callback as OP points out.当身份验证所需的字段丢失时, passport.authenticate将不会触发OP 指出的策略回调
This has to be handled inside the custom callback (scroll down page) in the authenticate function by using the info parameter.这必须通过使用info参数在身份验证函数中的自定义回调(向下滚动页面)中处理。
In case of the OP's code like so:如果是这样的 OP 代码:

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

I know this question is old but I have stumbled upon this issue myself and I see that there is still no accepted answer.我知道这个问题很老,但我自己偶然发现了这个问题,我发现仍然没有公认的答案。 Furthermore I think that all the answers misinterpreted what the OP was actually asking for - a way to access the badRequestMessage .此外,我认为所有的答案都误解了 OP 的实际要求 - 一种访问badRequestMessage
PassportJS docs are not very helpful either: PassportJS 文档也不是很有帮助:

If authentication failed, user will be set to false.如果身份验证失败,用户将被设置为 false。 If an exception occurred, err will be set.如果发生异常,将设置 err。 An optional info argument will be passed, containing additional details provided by the strategy's verify callback.将传递一个可选的 info 参数,其中包含策略的验证回调提供的其他详细信息。

What this actually means is that info parameter can be passed as a third parameter from your strategy like so: done(error,user,info) , but fails to mention that this parameter is used by default in case of missing credentials.这实际上意味着info参数可以作为您的策略中的第三个参数传递,如下所示: done(error,user,info) ,但没有提到在缺少凭据的情况下默认使用此参数。 Overall I think PassportJS docs could do with some overhaul as they lack detail and link to non-existent examples .总的来说,我认为 PassportJS 文档可以做一些大修,因为它们缺乏细节和指向不存在的示例的链接

This answer has helped me understand that the missing credentials message is passed in the info parameter. 这个答案帮助我理解在info参数中传递了缺少的凭据消息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM