Passport.js 身份验证失败时发回 JSON 响应

Posted

技术标签:

【中文标题】Passport.js 身份验证失败时发回 JSON 响应【英文标题】:Sending back a JSON response when failing Passport.js authentication 【发布时间】:2013-03-01 13:24:18 【问题描述】:

我正在使用Node.js 作为 iPhone 客户端的后端 API 服务器。我正在使用Passport.js 通过local strategy 进行身份验证。相关代码如下:

// This is in user.js, my user model
UserSchema.static('authenticate', function(username, password, callback) 
    this.findOne( username: username , function(err, user) 
        if (err)
            console.log('findOne error occurred');
            return callback(err);
        
        if (!user)
            return callback(null, false);
        
        user.verifyPassword(password, function(err, passwordCorrect)
            if (err)
                console.log('verifyPassword error occurred');
                return callback(err);
            
            if (!passwordCorrect)
                console.log('Wrong password');
                return callback(err, false);
            
            console.log('User Found, returning user');
            return callback(null, user);
        );
    );
);

// This is in app.js
app.get('/loginfail', function(req, res)
    res.json(403, message: 'Invalid username/password');
);

app.post('/login',
    passport.authenticate('local',  failureRedirect: '/loginfail', failureFlash: false ),
    function(req, res) 
       res.redirect('/');
);

现在,我已经成功地将失败的登录重定向到 /loginfail,在那里我将一些 JSON 发送回 iPhone 客户端。但是,这没有足够的粒度。我希望能够将适当的错误发送回 iPhone 客户端,例如:“未找到用户”或“密码错误”。使用我现有的代码,我看不出这是如何实现的。

我尝试在 passport.js 网站上按照示例进行自定义回调,但由于缺乏对节点的理解,我无法让它工作。如何修改我的代码,以便能够发回带有适当错误代码/消息的 res.json?

我现在正在尝试这样的事情:

// In app.js
app.post('/login', function(req, res, next) 
    passport.authenticate('local', function(err, user, info) 
        if (err)  return next(err) 
        if (!user) 
            console.log(info);
            // *** Display message without using flash option
            // re-render the login form with a message
            return res.redirect('/login');
        
        console.log('got user');
        return res.json(200, user_id: user._id);
    )(req, res, next);
);

// In user.js
UserSchema.static('authenticate', function(username, password, callback) 
    this.findOne( username: username , function(err, user) 
        if (err)
            console.log('findOne error occurred');
            return callback(err);
        
        if (!user)
            return callback(null, false);
        
        user.verifyPassword(password, function(err, passwordCorrect)
            if (err)
                return callback(err);
            
            if (!passwordCorrect)
                return callback(err, false, message: 'bad password');
            
            console.log('User Found, returning user');
            return callback(null, user);
        );
    );
);

但是当我尝试 console.log(info) 时,它只是说未定义。我不知道如何让这个自定义回调工作......任何帮助将不胜感激!

【问题讨论】:

我也被这个问题绊倒了一段时间。您的解决方案类似于 Passport.js 文档中的custom callback 自定义回调使您有责任建立会话。如果您想避免它,我通过模拟 flash 消息机制找到了一种解决方法(见下文) 【参考方案1】:

Passport 遇到了类似的问题,并且登录响应失败。我正在构建一个 API,并希望所有响应都返回为 JSON。 Passport 响应无效密码,状态为:401,正文:Unauthorized这只是正文中的一个文本字符串,而不是 JSON,所以它破坏了我的客户端,它期望所有的 JSON。

事实证明,有一种方法可以让 Passport 只将错误返回给框架,而不是尝试自己发送响应。

答案是在传递给身份验证的选项中设置failWithError: https://github.com/jaredhanson/passport/issues/126#issuecomment-32333163

来自 jaredhanson 在问题中的评论:

app.post('/login',
  passport.authenticate('local',  failWithError: true ),
  function(req, res, next) 
    // handle success
    if (req.xhr)  return res.json( id: req.user.id ); 
    return res.redirect('/');
  ,
  function(err, req, res, next) 
    // handle error
    if (req.xhr)  return res.json(err); 
    return res.redirect('/login');
  
);

这将在 Passport 调用 next(err) 后调用错误处理程序。对于我的应用程序,我编写了一个通用错误处理程序,专门针对仅提供 JSON 错误的用例:

// Middleware error handler for json response
function handleError(err,req,res,next)
    var output = 
        error: 
            name: err.name,
            message: err.message,
            text: err.toString()
        
    ;
    var statusCode = err.status || 500;
    res.status(statusCode).json(output);

然后我将它用于所有 api 路由:

var api = express.Router();
...
//set up some routes here, attached to api
...
// error handling middleware last
api.use( [
        handleError
        ] );

我没有在文档中找到failWithError 选项。我在调试器中跟踪代码时偶然发现了它。

另外,在我弄清楚这一点之前,我尝试了@Kevin_Dente 答案中提到的“自定义回调”,但它对我不起作用。我不确定这是针对旧版本的 Passport 还是我做错了。

【讨论】:

在 2017 年仍然有效 LOL 仍然有效。 :P 2021年仍然有效【参考方案2】:

我相信您的“身份验证”静态调用的回调函数(在您的代码中称为“回调”)接受您的代码可以提供的第三个参数 - “信息”。然后,不要传入 failureRedirect: ... 对象,而是传入一个接受 3 个参数的函数 - err、user 和 info。您在身份验证方法中提供的“信息”将传递给此回调。

Passport 将此场景称为“自定义回调”。请参阅此处的文档: http://passportjs.org/guide/authenticate/

【讨论】:

谢谢你。我知道自定义回调,但我的问题是我只是不明白如何实现它(对不起,节点和 js 很新)。我用我最近的努力编辑了我的问题。如您所见,我正在尝试使用此“信息”参数,但我不知道如何正确使用它——当我从身份验证返回时,它总是未定义。 抱歉,我刚刚发现我还需要在其中添加“信息”: passport.use(new LocalStrategy(function(username, password, done) User.authenticate(username, password , function(err, user, info) return done(err, user, info); ); )); 这次谈话帮助我得出了同样的结论,但有了更多的实质内容。 github.com/jaredhanson/passport/issues/255 @kurisukun 为什么你在passport.authenticate中通过了(req, res, next)? 对于未来,您能否添加一些代码 sn-ps 来澄清一下?没有它,这有点难以解析。【参考方案3】:

自定义回调有官方文档:

app.get('/login', function(req, res, next) 
  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);
);

https://github.com/passport/www.passportjs.org/blob/master/views/docs/authenticate.md

【讨论】:

【参考方案4】:

根据Passport的官方文档,您可以使用custom callback函数来处理授权失败的情况并覆盖默认消息。

如果您正在开发 REST API,然后您希望发送漂亮的 JSON 响应,如下所示:


    "error": 
        "name": "JsonWebTokenError",
        "message": "invalid signature"
    ,
    "message": "You are not authorized to access this protected resource",
    "statusCode": 401,
    "data": [],
    "success": false

我使用Passport JWT 身份验证来保护我的一些路由,并应用了authMiddleware,如下所示:

app/middlewares/authMiddleware.js

const express = require('express');
const router = express.Router();
const passport = require('passport');
const _ = require('lodash');

router.all('*', function (req, res, next) 
  passport.authenticate('local', function(err, user, info) 

    // If authentication failed, `user` will be set to false. If an exception occurred, `err` will be set.
    if (err || !user || _.isEmpty(user)) 
      // PASS THE ERROR OBJECT TO THE NEXT ROUTE i.e THE APP'S COMMON ERROR HANDLING MIDDLEWARE
      return next(info);
     else 
      return next();
    
  )(req, res, next);
);

module.exports = router;

app/routes/approutes.js

const authMiddleware = require('../middlewares/authMiddleware');

module.exports = function (app) 
  // secure the route by applying authentication middleware
  app.use('/users', authMiddleware);
  .....
  ...
  ..

  // ERROR-HANDLING MIDDLEWARE FOR SENDING ERROR RESPONSES TO MAINTAIN A CONSISTENT FORMAT
  app.use((err, req, res, next) => 
    let responseStatusCode = 500;
    let responseObj = 
      success: false,
      data: [],
      error: err,
      message: 'There was some internal server error',
    ;

    // IF THERE WAS SOME ERROR THROWN BY PREVIOUS REQUEST
    if (!_.isNil(err)) 
      // IF THE ERROR IS REALTED TO JWT AUTHENTICATE, SET STATUS CODE TO 401 AND SET A CUSTOM MESSAGE FOR UNAUTHORIZED
      if (err.name === 'JsonWebTokenError') 
        responseStatusCode = 401;
        responseObj.message = 'You are not authorized to access this protected resource';
      
    

    if (!res.headersSent) 
      res.status(responseStatusCode).json(responseObj);
    
  );
;

【讨论】:

【参考方案5】:

您可以在策略定义中使用属性passReqToCallback 来执行此操作,而无需自定义回调:

passport.use(new LocalStrategy(passReqToCallback: true, validateUserPassword));

然后您可以在策略代码中将您的自定义身份验证错误代码添加到请求中:

var validateUserPassword = function (req, username, password, done) 
    userService.findUser(username)
        .then(user => 
            if (!user) 
                req.authError = "UserNotFound";
                return done(null, false);
            

最后你可以在你的路由中处理这些自定义错误:

app.post('/login', passport.authenticate('local',  failWithError: true )      
    function (req, res) 
        ....
    , function(err, req, res, next) 
        if(req.autherror) 
            res.status(401).send(req.autherror)
         else 
            ....
        
    
);

【讨论】:

【参考方案6】:

一个简短的解决方法是模拟最初旨在支持连接闪存的 Flash 方法调用,并使用此方法返回 JSON 对象。

首先定义“模拟器”:

var emulateFlash = function (req, res, next) 
        req.flash = (type, message) => 
            return res.status(403).send( status: "fail", message );
        
        next();
    

这将注入 flash 方法,该方法将在失败时发送错误 JSON 对象。

在路线中执行以下操作:

1st,使用模拟器全面使用:

router.use(emulateFlash);

您可以在所需的每条路线上使用 emulateFlash 方法。

2、在路由上使用authenticate时,使用消息指定failureFlash选项:

router.route("/signin")
    .post(.authenticate('local',  session: false,  failureFlash: "Invalid email or password."), UsersController.signIn);

我对失败的身份验证和成功的身份验证都进行了测试,发现它可以正常工作。查看代码,除了实现需要更多工作的回调方法之外,我找不到任何其他方法来返回对象。

【讨论】:

以上是关于Passport.js 身份验证失败时发回 JSON 响应的主要内容,如果未能解决你的问题,请参考以下文章

Passport.js 在 req.login 上失败

Sails.js + Passport.js 通过 websockets 进行身份验证

Passport.js 可选身份验证

使用 Passport.js 验证角色和身份验证

我尝试使用 passport.js 对用户进行身份验证,但它给了我一个错误**服务器响应状态为 400(错误请求)**

Express Sequelize 和 Passport.js 身份验证策略不起作用