如何将 oauth2 令牌发送给客户端并存储它们?

Posted

技术标签:

【中文标题】如何将 oauth2 令牌发送给客户端并存储它们?【英文标题】:How to send oauth2 tokens to client and store them? 【发布时间】:2018-04-24 06:24:51 【问题描述】:

我有一个应用程序,我想使用 3rd 方 API(例如 Google、Facebook 等)登录。客户端前端只是一个将与我的服务器交互的 javascript SPA。服务器本质上只需要存储第三者ClientIDClientSecret

要登录,客户端会将用户链接到MyAPI/login。然后,MyAPI 会将用户重定向到第 3 方登录页面以及 ClientID。身份验证后,第 3 方将使用 code 查询参数将用户重定向回 MyAPI/callback。 MyAPI 会将此code 连同ClientIDClientSecret 一起发送回第3 方API。第 3 方 API 最终将返回 access_tokenrefresh_token

我的问题是我应该如何将令牌发送回客户端应用程序?而且,一旦客户有了令牌,我应该如何存储它们?

【问题讨论】:

您可以使用Set-Cookie http 标头。 Docs 这里。 @csm_dev 谢谢,我继续做了。我正在使用Set-Cookie 为客户端创建一个临时cookie,然后将令牌存储在本地存储中。将普通令牌存储在 localStorage 中是否存在安全风险? @getmicah 总是有风险的,但这是一个可以接受的放置它们的地方。本文概述了不同的方法及其优缺点。他们推荐 cookie,但我认为很多人确实使用 local/sessionStorage。 stormpath.com/blog/… 【参考方案1】:

您所描述的是 OAuth 的授权代码授予流程。在 Auth Code 流程中,向客户端(在本例中为 MyAPI)提供了一个代码,以便用户永远不会收到 access_tokenrefresh_token,因此不必信任它们。

通过向用户提供这些令牌并允许他们将其存储在本地存储中,您正在规避身份验证代码流的安全优势。除非安全对您来说不是那么大的问题(您信任用户),否则绝对不建议这样做。

正如 cmets 所建议的,您可以使用 cookie 来保持与 MyAPI 的会话。尽管如此,access_tokenrefresh_token 应该保留在会话数据中,而不是直接与用户共享。这强制要求只能由 MyAPI 代表用户访问第 3 方 API。

在此问题的公认答案中提供了对 Auth Code 流程的更好解释:What is the difference between the 2 workflows? When to use Authorization Code flow?

MyAPI 的一个不完整的 Express.js 示例:

// imports
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const jwt = require('jsonwebtoken');

// setup passport (an authentication middleware) and use it with the session provided by express-session
passport.serializeUser((user, done) => 
  done(null, user);
);
passport.deserializeUser((obj, done) => 
  done(null, obj);
);
passport.use(new SomeStrategy(...strategyOptions));
const app = express();
app.use(passport.initialize());
app.use(passport.session());

// route handlers
app.get('/login', passport.authenticate('SOME_STRATEGY'), () => );
app.get('/callback', passport.authenticate('SOME_STRATEGY'),  failureRedirect: '/badlogin' , (req, res) => res.send(200));
app.get('/resource', (req, res) => 
  const accessToken = req.user.access_token; // req.user has the session information including the access token
  try 
    // verify the access token with the 3rd party auth's public key (NOT THE SAME AS DECODING IT!) 
    const decodedAccessToken = jwt.verify(accessToken, thirdPartAuthPublicKey);
    return res.send(200).send(decodedAccessToken);
   catch (err) 
    return res.send(401).send('unauthenticated');
  
);

这里,服务器将access_token 保存在用户的会话数据中(req.user)。当用户请求资源时,服务器会尝试验证access_token并返回其解码后的内容。

【讨论】:

忽略最后一条评论。你怎么看这个流量? tools.ietf.org/html/rfc6749#section-4.1 感谢示例代码。还有一个问题:是否可以将令牌存储在 HttpOnly Cookie 中以便只有服务器可以读取它?这样用户就不必每次关闭会话时都重新登录? 我正在使用这个:github.com/gorilla/securecookie 并启用 HttpOnly 和 Secure 标志。存储在用户机器上的 cookie 是加密的,这应该可以防止用户在向 MyAPI 发出请求之前操纵访问代码 在服务器端使用授权码授权类型生成访问令牌,然后将其传递给客户端(浏览器/本机应用程序)进行存储,这不是一种不好的做法吗?这个link 也说明了这一点。很想听到回音。 是的,访问令牌不应传递给客户端。它的解码内容可以被传递(例如,前端需要呈现的会话状态)。

以上是关于如何将 oauth2 令牌发送给客户端并存储它们?的主要内容,如果未能解决你的问题,请参考以下文章

gdata OAuth2如何使用多个用户的令牌

spring oauth2 令牌缓存

如何存储访问令牌? (Oauth 2,Auth 代码流程)

将 OAuth2 访问令牌配置为 typescript-angular2 客户端

Oauth2令牌 - 我可以将令牌存储在会话中

如何使用 Angular 2 发送 OAuth2 的客户端 ID 和秘密 ID?