为什么刷新令牌在单页应用程序中被认为是不安全的?

36
我正在阅读Auth0网站关于刷新令牌和单页应用程序(SPA)的文档,他们指出SPA不应该使用刷新令牌,因为它们不能在浏览器中安全地存储,而是应该使用静默身份验证来检索新的访问令牌。
引用部分: 通常实现隐式授权的单页应用程序不得在任何情况下获取刷新令牌。原因是这一信息的敏感性。您可以将其视为用户凭据,因为刷新令牌使用户能够长期保持认证状态。因此,您不能将此信息存储在浏览器中,必须进行安全存储。
我感到困惑。据我了解,检索新的访问令牌的唯一方法就是向认证服务器提交新的请求,同时携带某种形式的Auth0会话cookie对已登录的用户进行身份验证。在接收到会话cookie后,Auth0服务器将能够发出新的访问令牌。
但是,与在浏览器或本地存储中有刷新令牌相比,这有何不同呢?什么使会话Cookie比刷新令牌更安全?为什么在SPA中使用刷新令牌是不好的事情?

它说客户端不应该接收刷新令牌,而不是不应该使用。它们只是不应该存储在客户端。 - fylzero
至于为什么这是一件坏事...它就在那里说了..."刷新令牌允许用户保持身份验证,基本上是永久的。" 这就是潜在的缺点。 - fylzero
5个回答

24
关于cookies、refresh tokens和OAuth2,有很多误解。首先,只有机密客户端才能使用刷新令牌是不正确的。OAuth2协议规定机密客户端必须进行身份验证,但并不要求机密客户端。因此,在刷新操作中,客户端身份验证是可选的。详见RFC 6749,第6节,刷新访问令牌。其次,你必须了解备选方案:1. 强制用户每5分钟(每当访问令牌过期时)输入用户名和密码;2. 长期访问令牌;3. HTTP Cookie身份验证。全世界不使用刷新令牌的人都使用选项3。通过cookie进行身份验证在功能和安全方面与存储刷新令牌100%等效。当然,对于令牌和cookie,都有保存它们的选项:a. 仅限HTTP,b. 安全(需要TLS/SSL),c. 会话(内存中)vs. 持久性(本地,域存储)。"仅限HTTP"选项仅适用于cookie,因此可能是使用cookie而不是令牌的唯一优势。也就是说,令牌通过Javascript处理,因此没有将其远离脚本的选项。尽管如此,令牌仅对存储它的页面所在的域中的Javascript可用(或根据CORS策略允许的方式)。因此,这个问题可能被夸大了。
当然,必须小心地始终使用TLS / SSL传输身份验证cookie或令牌。老实说,由于我们知道大多数违规行为发生在私人企业网络内部,因此端到端的TLS现在是基本要求。
最后,无论cookie还是令牌是否被持久化,即存储在关闭浏览器甚至重新启动设备后仍然存在的位置,取决于您在可用性和安全性之间做出的权衡-对于您的应用程序而言。
对于需要更高安全级别的应用程序,只需将所有内容保存在内存中(即会话cookie,在JavaScript变量中的令牌)。但对于不需要太多安全性并且确实希望会话寿命达到几天或几周的应用程序,则需要将其存储。无论哪种方式,该存储仅对来自原始域的页面和脚本可访问,因此cookie和令牌在功能上是等效的。

1
通过 cookie 认证在功能和安全方面与存储刷新令牌完全等效。但我不认为这是正确的。刷新令牌存储在 spa 所在的域中,而身份验证 cookie 存储在登录页面所在的域中。在使用 #3 时,您会暂时重定向回登录页面(通常使用 iframe),以使用身份验证代码流刷新令牌。在我看来,这正是它比存储刷新令牌更安全的原因。 - Johan t Hart
@JohantHart 令牌域的详细信息取决于身份验证流程。但是,是的,我们注意到了。SPA需要访问存储令牌的位置,以便将其传递给REST请求标头。无论哪种方式,您仍然信任浏览器来保护存储。在我看来,我见过很多误导性反对使用cookie的情况。实际上,令牌不受XSRF / cookie攻击的影响。因此,仍然需要考虑权衡。但是,使用cookie + XSRF进行身份验证,使用令牌进行应用程序访问是一种可靠的方法。 - Charlie Reitzel
有额外的安全级别。使用不透明令牌的方式越来越普遍。 以令牌处理程序模式为例,可以参考以下链接: https://curity.io/resources/learn/the-token-handler-pattern/ - undefined
就我所知,拥有一个处理令牌的前端服务并不是一个新的想法。明显的缺点是现在你需要在你的REST API上添加CSRF保护。好处是,如果你在自己的Javascript依赖中无意中包含了恶意代码,它将无法访问HTTP-only cookie,因此更符合"零信任"的要求。考虑到我们都在使用庞大的依赖树,这是一个非常重要的考虑因素。如果代码已经存在,构建时的扫描是无济于事的。 - undefined

16

这已不再是事实(2021年4月),Auth0网站现在提供了不同的建议:

Auth0建议使用刷新令牌轮换来为单页应用程序提供安全的刷新令牌使用方法,同时通过避免浏览器隐私技术(如ITP)造成的UX中断,为终端用户提供无缝访问资源。

Auth0以前的指导是,在SPAs中结合静默认证使用授权码流程和Proof Key for Code Exchange(PKCE)。这比Implicit Flow更安全,但不如结合刷新令牌轮换的授权码流程和Proof Key for Code Exchange(PKCE)安全。

请注意启用刷新令牌轮换的重要性。


1
使用刷新令牌轮换,访问令牌和刷新令牌的生命周期应该是多久?或者说最安全的时间是什么? - Cathal Mac Donnacha

13
在单页应用程序中不使用刷新令牌,因为为了使用它并从/token获取新的访问令牌,SPA需要具有客户端秘钥,该秘钥无法在浏览器中安全存储。但是由于OAuth 2.0原生应用程序RFC建议不要对/token端点(针对公共客户端)要求客户端秘钥,因此即使在单页应用程序中也可以使用刷新令牌。

要获取刷新令牌,需要使用授权代码授权,该授权通过重定向URL中的代码进行传递,该URL转到托管SPA的服务器(可能是攻击的额外攻击点)。隐式授权仅将令牌传递给浏览器(重定向URL的哈希部分不会到达服务器)。

使用刷新令牌和SSO会话cookie之间的区别- cookie可能更安全,因为可以将其标记为HttpOnly,使其无法通过JavaScript代码进行攻击。

更新

使用PKCE扩展,授权代码流程(带有刷新令牌)甚至成为针对基于浏览器的应用程序的推荐流程。详细信息请参见OAuth 2.0 for Browser-Based Apps RFC的最新版本。


在PKCE的情况下,grant_type=refresh_token需要哪些参数?@ján-Halaša - user606669
PKCE 定义了三个参数 - code_challengecode_challenge_methodcode_verifier,需要知道它们的工作原理。如果没有在初始授权调用中使用前两个参数,则无法仅将其用于令牌端点调用。 - Ján Halaša
我已经使用那些参数进行了初始认证调用,并存储了code_challenge,code_challenge_method。但是我找不到有关refresh_token调用所需内容的任何文档,因为对于SPA,我无法发送client_secret,我不确定需要哪些参数。您是在建议我应该在续订令牌时连同client_id发送code_verifier吗? - user606669
@user606669 你最终解决了如何在初始的 /authorize 调用中进行刷新吗? - user1110790
@user1110790 我理解的是,一旦您获得了刷新令牌,您只需将 refresh_token 与 clientId 一起发送,即可获取新的 (access_token 和 id_token)。 - user606669
请按照图表进行初始调用 https://auth0.com/docs/flows/concepts/auth-code-pkce - user606669

8

好问题 - 所以没有真正安全的方法可以在浏览器(或任何其他机密信息)上存储任何令牌 - 参见链接。因此,单页应用程序(SPA)不应存储刷新令牌 - 刷新令牌尤其有问题,因为它具有长期生存时间(长到期或无到期),如果被盗,则攻击者可以在每个访问令牌单独到期后继续刷新访问令牌。

最好只在需要访问令牌时检索它(例如调用API),并且只能将其存储在内存中(仍易受XSS / CSRF攻击)但更好 - 或者使用并忘记。然后下次需要访问令牌时进行另一个checkSession调用。

至于您的问题 - checkSession 请求不需要发送令牌。正如名称所示,它是针对授权服务器进行“检查会话”,以查看是否存在会话。如果存在,则授权服务器响应将包括新的访问令牌。请参阅此处的SPA示例用法。

如果需要更多澄清等内容,请在本回答下方留下评论。


4
我仍然不理解为什么会话Cookie比刷新令牌更好。为了在没有刷新令牌的情况下获取新的访问令牌,我仍然需要提交会话Cookie。为什么会话Cookie被认为比刷新令牌更好呢?会话Cookie与刷新令牌具有相同的问题(长期有效,并可用于生成新的访问令牌)。那么,为什么使用会话Cookie被认为是“安全”的,而使用刷新令牌则不是呢? - Eric B.
Eric B. 作者没有提到的是,您现在正在使用一个会话 cookie,可以附带一个防伪标记。以下是有关使用防伪标记预防 CSRF 的更多信息。https://learn.microsoft.com/zh-cn/aspnet/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks - prison-mike
1
将刷新令牌存储在 cookie 中并正确保护 cookie,例如设置 Http 标志。 - prasanthv
如果我将refresh_token加密存储在cookie或服务器中,只有在需要时通过后端由服务器生成一个1分钟有效期的access_token(用于访问其他资源),这样做是否合理? - Yiping
会话 cookie 不会被持久存储。因此,如果您关闭浏览器,您需要重新登录(输入用户名和密码等)。如果您正在使用 cookie 进行身份验证,则应使用 OIDC 隐式流程来(在同意后)获取用于 REST API 使用的访问令牌。这使您可以将 cookie 和 CSRF 保护限制为您的登录服务器,该服务器可能位于与您的 REST 服务不同的域名下。例如,login.example.com vs. api.example.com。 - Charlie Reitzel

0

我们可以将刷新令牌存储在带有 httpOnly、secure 和 signed 标志设置为 true 的 cookie 中。

在 Node.js 中设置刷新令牌的示例

const refreshToken = signToken(userId)
const cookieName = "foo-bar-blah"

res.cookie(cookieName , refreshToken , {
      httpOnly: true,
      signed: true,
      secure: true
    })

我们所要做的就是在客户端发送包含刷新令牌的cookie请求,而无需访问它。使用像axios这样的库进行客户端请求。
await axios.get('refresh-endpoint', {
    withCredentials: true
})

将withCredentials设置为true会将浏览器的cookie与请求一起发送。

每当客户端在服务器上访问refresh-endpoint路由时,使用存储在服务器上的密钥对cookie进行解密,并检索刷新令牌。

这种设置使得客户端JavaScript很难访问刷新令牌。即使可能存在这种可能性,仍然存在从cookie中检索刷新令牌的问题,而没有服务器上的cookie密钥。

我认为这是相当安全的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接