使用 Passport 和 OAuth2 + 社交网络的 NodeJS REST 身份验证

Posted

技术标签:

【中文标题】使用 Passport 和 OAuth2 + 社交网络的 NodeJS REST 身份验证【英文标题】:NodeJS REST authentication using Passport and OAuth2 + social network 【发布时间】:2014-12-04 19:26:22 【问题描述】:

我正在使用NodeJS 开发REST api。对于身份验证,我决定使用Passport。我想要真正的 RESTful api。所以这意味着我必须使用令牌而不是会话。

我想让用户使用用户名和密码登录,或者使用 Facebook、Google 和 Twitter 等社交网络。

我制作了自己的OAuth2.0 服务器,用于使用oauth2orize 模块发布AccessRefresh tokens。所以现在我可以注册新用户,然后给他们发放令牌。 我遵循了本教程:

http://aleksandrov.ws/2013/09/12/restful-api-with-nodejs-plus-mongodb/

验证用户的路线:

// api ------------------------------------------------------------------------------------
    app.get('/api/userInfo',
        passport.authenticate('bearer',  session: false ),
        function(req, res) 
            // req.authInfo is set using the `info` argument supplied by
            // `BearerStrategy`.  It is typically used to indicate scope of the token,
            // and used in access control checks.  For illustrative purposes, this
            // example simply returns the scope in the response.
            res.json( user_id: req.user.userId, name: req.user.username, scope: req.authInfo.scope )
        
    );

这一切都很好。不幸的是,我不知道如何实现社交身份验证。

我正在阅读本教程:

http://scotch.io/tutorials/javascript/easy-node-authentication-facebook 

但在本教程中,他们并没有制作真正的 RESTful api。我已经根据本教程实现了用户模式,其中本地用户的令牌存储在单独的模型中。

// define the schema for our user model
var userSchema = mongoose.Schema(
    local: 
        username: 
            type: String,
            unique: true,
            required: true
        ,
        hashedPassword: 
            type: String,
            required: true
        ,
        created: 
            type: Date,
            default: Date.now
        
    ,
    facebook: 
        id: String,
        token: String,
        email: String,
        name: String
    ,
    twitter: 
        id: String,
        token: String,
        displayName: String,
        username: String
    ,
    google: 
        id: String,
        token: String,
        email: String,
        name: String
    
);

但是现在,我该如何验证用户呢?

passport.authenticate('bearer',  session: false ),

这只是针对我的数据库验证不记名令牌,但我如何验证社交令牌?我错过了什么吗?

【问题讨论】:

您能详细说明您最终是如何解决这个问题的吗?您是否也可以留下您的 Twitter 句柄以便进一步通信。谢谢:) 而不是aleksandrov教程,为什么不能使用passport-oauth? passportjs.org/guide/oauth 为什么还要为自己的服务器实施 oAuth?为什么不使用本地护照,然后通过不记名策略发行令牌? 【参考方案1】:

护照的社交媒体策略取决于会话。没有一个,它们就无法发挥作用。

我遇到了同样的问题,我希望我的 REST 服务器是无状态的。

我认为有两种选择。

    将会话添加到您的休息服务器 添加一个新的身份验证服务器,负责创建和分发令牌。

PS:您应该将其标记为护照,以便通过端口开发人员可以看到它。

【讨论】:

为什么不能使用passport-oauth而不是aleksandrov教程(aleksandrov.ws/2013/09/12/restful-api-with-nodejs-plus-mongodb)? passportjs.org/guide/oauth 这是本教程在后台使用的模块。它是使用会话的那个。令牌存储在会话中,因此当浏览器重定向时,它可以关联返回的用户/令牌。因此,我的身份验证服务是有状态的,而我的 api 是无状态的。【参考方案2】:

我创建了以下架构:

var userSchema = mongoose.Schema(
    local: 
        username: String,
        password: String
    ,
    facebook: 
        id: String,
        token: String,
        email: String,
        name: String
    ,
    google: 
        id: String,
        token: String,
        email: String,
        name: String
    ,
    token: 
        type: Schema.Types.ObjectId,
        ref: 'Token',
        default: null
    
);

var tokenSchema = mongoose.Schema(
    value: String,
    user: 
        type: Schema.Types.ObjectId,
        ref: 'User'
    ,
    expireAt: 
        type: Date,
        expires: 60,
        default: Date.now
    
);

登录到我的网络应用程序时,我使用像 facebook/google 这样的 PassportJS 社交插件。示例:

//auth.js(router)
router.get('/facebook', passport.authenticate('facebook', scope: ['email']));

router.get('/facebook/callback', 
  passport.authenticate('facebook',  successRedirect: '/profile',
                                      failureRedirect: '/' ));

现在,当我想浏览我的 Web 应用程序时,我使用通过 Facebook 插件提供给我的会话身份验证。当用户想要请求 API 令牌时,他们需要登录,以便我可以将该令牌与用户关联。

所以现在用户有一个与他们关联的令牌,他们可以将其用于我的 API 的令牌身份验证。

我的 API 路由不关心或查看会话,他们只关心令牌。我通过创建一个快速路由器并使用护照的承载策略作为通往我的 API 的所有路由的中间件来做到这一点。

//api.js (router)
//router middleware to use TOKEN authentication on every API request
router.use(passport.authenticate('bearer',  session: false ));

router.get('/testAPI', function(req, res)
        res.json( SecretData: 'abc123' );
    );

所以现在我只对我的 API 使用令牌身份验证(从不查看会话数据),并使用会话身份验证来轻松导航我的 web 应用程序。我使用会话来导航我的应用程序的示例如下所示:

//secure.js(router) - Access private but NON-API routes.
//router middleware, uses session authentication for every request
router.use(function(req, res, next)
        if(req.isAuthenticated())
            return next();
        
        res.redirect('/auth');
    );
//example router
router.get('/profile', function(req, res)
        res.send("Private Profile data");
    );

希望对您有所帮助!

【讨论】:

再次感谢!是的,我理解了你的概念。然而,这并没有使后端成为纯 RESTful - 没有来自 android 设备的 facebook auth 入口点。应该有一个 RESTful 机制来做到这一点。在此之后,您的解决方案是完美的,因为不记名令牌会接管以生成应用程序特定的令牌。因此我在你的视频中留下了这个想法youtube.com/watch?v=VVFedQZgUgw【参考方案3】:

如果您使用不记名令牌,您只需将唯一标识符传递给 API 的用户。这很可能在一个单一的调用中完成,可能是以登录的形式。然后,对 API 的每次调用都需要该令牌来验证数据库中是否存在令牌并更新其生存时间值。对于每个登录,您的架构中不需要单独的令牌,您需要为具有生存时间值 (TTL) 的令牌提供单独的架构

【讨论】:

【参考方案4】:

我正在为我自己的my Notepads app here 的 RESTful API 使用 Facebook 登录。我将应用程序启动为将用作网页的应用程序,但登录后的通信仍将通过 API。

然后我决定创建一个使用 API 的mobile version of the same app。我决定这样做:移动应用程序通过 Facebook 登录并将 facebook 用户 ID 和 FB 访问令牌发送到 API,API 调用 Facebooks 的 API 来验证这些参数,如果成功注册新用户(或登录现有的)在我的应用程序的数据库中,为该用户创建一个自定义令牌并将其返回给移动应用程序。移动应用从这里发送此自定义令牌以使用 API 对应用进行身份验证。

这里有一些代码:

API 中的 auth(使用 fbgraph npm 模块):

var graph = require('fbgraph'),
Promise = require('bluebird')
...
Promise.promisify(graph.get);
...
var postAuthHandler = function (req, res) 
    var fbUserId = req.body.fbId,
    fbAccessToken = req.body.fbAccessToken,
    accessToken = req.body.accessToken;
    ...
    graph.setAppSecret(config.facebook.app.secret);
    graph.setAccessToken(fbAccessToken);

    var graphUser;
    var p = graph.getAsync('me?fields=id,name,picture')
        .then(function (fbGraphUser) 
            //when the given fb id and token mismatch:
            if (!fbGraphUser || fbGraphUser.id !== fbUserId) 
                console.error("Invalid user from fbAccessToken!");
                res.status(HttpStatus.FORBIDDEN).json();
                return p.cancel();
            

            graphUser = fbGraphUser;

            return User.fb(fbUserId);
        )
        .then(function (user) 
            if (user) 
                //user found by his FB access token
                res.status(HttpStatus.OK).json(accessToken: user.accessToken);
                //stop the promises chain here
                return p.cancel();
            
            ...create the user, generate a custom token and return it as above...

https://github.com/iliyan-trifonov/notepads-nodejs-angularjs-mongodb-bootstrap/blob/6617a5cb418ba8acd6351ef9a9f69228f1047154/src/routes/users.js#L46.

用户模型:

var userSchema = new mongoose.Schema(
    facebookId:  type: String, required: true, unique: true ,
    accessToken:  type: String, required: true, unique: true ,
    name:  type: String, required: true ,
    photo:  type: String, required: true ,
    categories: [ type: mongoose.Schema.Types.ObjectId, ref: 'Category' ],
    notepads: [ type: mongoose.Schema.Types.ObjectId, ref: 'Notepad' ]
);

https://github.com/iliyan-trifonov/notepads-nodejs-angularjs-mongodb-bootstrap/blob/master/src/models/user.js#L9.

移动应用中的 Facebook 身份验证:

               auth: function(fbId, fbAccessToken) 
                return $http(
                    url: apiBase + '/users/auth',
                    data: 
                        fbId: fbId,
                        fbAccessToken: fbAccessToken
                    ,
                    method: 'POST',
                    cache: false
                );
            ,
            ...

https://github.com/iliyan-trifonov/notepads-ionic/blob/master/www/js/services.js#L33.

移动应用程序随请求发送令牌:

  notepads: 
            list: function() 
                return $http(
                    url: apiBase + '/notepads?insidecats=1' + '&token=' + User.get().accessToken/*gets the token from the local storage*/,
                    method: 'GET',
                    cache: false
                );
            ,

这是一个 Ionic/Angular/Cordova 应用程序。从移动应用程序登录 Facebook 会启动您手机上安装的 Facebook 应用程序或打开一个弹出窗口以登录 Facebook。然后回调将 Facebook 用户的 id 和访问令牌返回到我的移动应用程序。

fbgraph npm 模块:https://github.com/criso/fbgraph

【讨论】:

只需格式化 URL,它们就会处于活动状态。无需权限。 谢谢@NickVolynkin!昨天我只能使前 2 个 url 处于活动状态,并且收到了我需要至少 10 个代表的警报。对于其余的。但是今天我有了它并成功格式化了网址。

以上是关于使用 Passport 和 OAuth2 + 社交网络的 NodeJS REST 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何使用分离的后端和前端(Passport / Express / React)进行社交身份验证

Node、Express、Oauth2 和 Passport 的 Angularjs CORS 问题

Passport (oAuth2) 如何与 GraphQL (TypeGraphQL) 一起使用?

使用 Passport.js 的 Vue.js 客户端和 Express.js 服务器的完整 Oauth2 身份验证流程

如何在使用 Passport 进行社交身份验证后重定向到正确的客户端路由(react、react-router、express、passport)

如何使用 OAuth2 和社交登录保护 Spring Boot RESTful 服务