使用passport.js 和express.js (node.js) 制作一个安全的oauth API

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13933980/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 16:47:21  来源:igfitidea点击:

Make a secure oauth API with passport.js and express.js (node.js)

node.jsapiexpressoauthpassport.js

提问by Soundstep

I've got I think a specific problem, unless I'm not doing things in the right way.

我有一个特定的问题,除非我没有以正确的方式做事。

I've got an application with 2 sides, a client (html site), and an API (built with express + mongodb). The API will need to be securely accessed. They both are on different domains, for now let's say my site is on domain.com and my API is on api.com:3000.

我有一个有两个方面的应用程序,一个客户端(html 站点)和一个 API(用 express + mongodb 构建)。需要安全地访问 API。它们都在不同的域中,现在假设我的站点位于 domain.com 上,而我的 API 位于 api.com:3000 上。

I've added passport to get an access token from Github as I'm creating my users with their data, so I can have a "Sign in with Github" basically.

我已经添加了通行证以从 Github 获取访问令牌,因为我正在使用他们的数据创建我的用户,所以我基本上可以有一个“使用 Github 登录”。

My current process is:

我目前的流程是:

1) the client (the site), opens a popup on the API

1) 客户端(站点),在 API 上打开一个弹出窗口

window.open("http://api.com:3000/oauth")

2) The express server, start the passport process:

2)快递服务器,启动通行证流程:

app.get('/oauth', passport.authenticate('github'), function(req, res) {

});

For the strategy, I used this code.

对于策略,我使用了这个代码

3) I redirect the callback to a url that closes the popup (javascript with a window.close()):

3)我将回调重定向到关闭弹出窗口的网址(带有window.close()的javascript):

app.get('/oauth/callback', passport.authenticate('github', { failureRedirect: '/' }), function(req, res) {
    res.redirect('/auth_close');
});

At that point, all is fine, I am now logged in the API. My problem is how to get data back to the client, as the client doesn't know anything yet about accessToken, user or user id.

那时,一切都很好,我现在登录了 API。我的问题是如何将数据返回给客户端,因为客户端对 accessToken、用户或用户 ID 一无所知。

So, from the client (the site that opens the popup), I tried different approaches:

因此,从客户端(打开弹出窗口的站点),我尝试了不同的方法:

  • getting a value from the popup: doesn't work because of the redirection, I lose track of the popup javascript information

  • calling my API to get the "current user" in session, such as http://api.com:3000/currentOne more request, not ideal, but this one work. However, I still have a problem.

  • 从弹出窗口中获取一个值:由于重定向而不起作用,我丢失了对弹出窗口 javascript 信息的跟踪

  • 调用我的 API 来获取 session 中的“当前用户”,例如http://api.com:3000/current 还有一个请求,不理想,但是这个有效。但是,我仍然有问题。

This /current url is returning the user if I reach it from the browser, because the browser send in the header request the express session cookie:

如果我从浏览器访问它,这个 /current url 将返回用户,因为浏览器在标头请求中发送了快速会话 cookie:

Cookie:connect.sid=s%3AmDrM5MRA34UuNZqiL%2BV7TMv6.Paw6O%2BtnxQRo0vYNKrher026CIAnNsHn4OJdptb8KqE

The problem is that I need to make this request from jquery or similar, and that's where it fails because the session is not sent. So the user is not returned:

问题是我需要从 jquery 或类似的地方发出这个请求,这就是它失败的地方,因为没有发送会话。所以用户没有返回:

app.get('/current', function() {
    // PROBLEM HERE, req.user is undefined with ajax calls because of the session!
});

I found a way of making it work but I'm not happy with because I'll have cross-browsers CORS problems, it is adding to express:

我找到了一种使它工作的方法,但我不满意,因为我会遇到跨浏览器的 CORS 问题,它增加了表达:

res.header("Access-Control-Allow-Credentials", "true");

And add the withCredentials field in a jquery ajax call, as explained here.

并在 jquery ajax 调用中添加 withCredentials 字段,如解释here

A map of fieldName-fieldValue pairs to set on the native XHR object. For example, you can use it to set withCredentials to true for cross-domain requests if needed.

要在本机 XHR 对象上设置的 fieldName-fieldValue 对的映射。例如,如果需要,您可以使用它为跨域请求将 withCredentials 设置为 true。

$.ajax({
   url: a_cross_domain_url,
   xhrFields: {
      withCredentials: true
   }
});

I am also not happy with this as I'm losing the wildcard on my header: Access-Control-Allow-Origin, I need to specify a domain, as explained here.

我也不满意,这是我失去了我的头通配符:Access-Control-Allow-Origin我需要指定域,作为解释在这里

So, I'm not sure the approach I should take here, the only thing I need is the client getting either an accessToken, or a userID back from the passport oauth process. The idea is using that token in each call to the API to validate the calls.

所以,我不确定我应该在这里采取什么方法,我唯一需要的是客户端从护照 oauth 过程中获取 accessToken 或用户 ID。这个想法是在每次调用 API 时使用该令牌来验证调用。

Any clue?

有什么线索吗?

回答by Scott Switzer

I had the same issue. I was using the Local Strategy on the login page, and then checking to see if the user was on the session on other requests.

我遇到过同样的问题。我在登录页面上使用本地策略,然后检查用户是否在其他请求的会话中。

As you say, the solution is to use CORS in order for the session ID to be passed in a cookie using XMLHTTPRequest.

正如您所说,解决方案是使用 CORS,以便使用 XMLHTTPRequest 在 cookie 中传递会话 ID。

Instead of using the CORS which does not yet work on all browswers, I deceided to use access tokens on other requests. The workflow I used is as follows:

我没有使用尚未在所有浏览器上运行的 CORS,而是决定在其他请求上使用访问令牌。我使用的工作流程如下:

POST /login
  • Username and password get passed in the body.
  • Authentication using Local Strategy.
  • Response returns the user object, including the access_token
  • 用户名和密码在正文中传递。
  • 使用本地策略进行身份验证。
  • 响应返回用户对象,包括 access_token

GET /endpoint/abc123?access_token=abcdefg

GET /endpoint/abc123?access_token=abcdefg

  • Token obtained from the login response
  • Authentication using Bearer Strategy (in passport-http-bearer)
  • 从登录响应中获得的令牌
  • 使用承载策略的身份验证(在passport-http-bearer中)

Sessions are now not needed in Express.

Express 中现在不需要会话。

I hope this alternative helps.

我希望这个替代方法有帮助。

回答by sam100rav

Before configuring anything in express app, use the following(exactly the same) to set header of response for cross-domain :

在 express 应用程序中配置任何内容之前,请使用以下(完全相同)设置跨域响应头:

app.use(function(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
if ('OPTIONS' == req.method) {
     res.send(200);
 } else {
     next();
 }
});