ChatGPT解决这个技术问题 Extra ChatGPT

在客户端的哪里存储刷新令牌?

我的 SPA 应用程序使用以下架构 (source):

https://i.stack.imgur.com/BPJjA.png

这假设我的客户端应用程序知道刷新令牌,因为如果不存在用户凭据(例如电子邮件/密码),我需要它来请求新的访问令牌。

我的问题:在我的客户端应用程序中,我在哪里存储刷新令牌? SO上有很多关于这个主题的问题/答案,但关于刷新令牌,答案并不清楚。

访问令牌和刷新令牌不应存储在本地/会话存储中,因为它们不是任何敏感数据的地方。因此,我会将 访问令牌 存储在 httpOnly cookie 中(即使有 CSRF),无论如何我对资源服务器的大部分请求都需要它。

但是刷新令牌呢?我不能将它存储在 cookie 中,因为 (1) 它会随每个请求一起发送到我的资源服务器,这也使其容易受到 CSRF 的攻击;(2) 它会发送具有相同攻击向量的公开访问/刷新令牌.

我能想到三个解决方案:

1) 将刷新令牌存储在内存中的 JavaScript 变量中,这有两个缺点:

a) 它易受 XSS 攻击(但可能不像本地/会话存储那样明显

b)如果用户关闭浏览器选项卡,它会丢失“会话”

特别是后一个缺点会导致糟糕的用户体验。

2) 将访问令牌存储在会话存储中并通过 Bearer access_token 授权标头将其发送到我的资源服务器。然后我可以使用 httpOnly cookie 作为刷新令牌。这有一个我能想到的缺点:

a) 每次向资源服务器发出请求时,刷新令牌都会暴露给 CSRF。

3) 将两个令牌都保存在 httpOnly cookie 中,这具有上述缺点,即两个令牌都暴露于相同的攻击向量。

也许除了我提到的缺点之外,还有其他方法或更多(请告诉我),但最终一切都归结为我在哪里将我的刷新令牌保存在客户端?它是 httpOnly cookie 还是内存中的 JS 变量?如果是前者,那我应该把我的访问令牌放在哪里?

非常乐意从熟悉该主题的人那里获得有关如何以最佳方式做到这一点的任何线索。

提供更多信息。这是 SPA 还是基于服务器的应用程序?
在这种情况下,它是一个 SPA。
想知道您实施的解决方案是什么?我目前正在使用选项 2,而不用担心 CSRF。刷新令牌在每个 SSR 上都使用并失效。但是,当 SPA 在多个选项卡中打开时,这会导致访问令牌不同步。

V
Vlad DX

您可以将加密令牌安全地存储在 HttpOnly cookie 中。

https://medium.com/@sadnub/simple-and-secure-api-authentication-for-spas-e46bcea592ad

如果您担心长期存在的刷新令牌。您可以跳过存储它并且根本不使用它。只需将 Access Token 保存在内存中,并在 Access Token 过期时进行静默登录。

不要使用隐式流,因为它已经过时了。

SPA 最安全的身份验证方式是 Authorization Code with PKCE

一般来说,使用基于 oidc-client 的现有库比自行构建要好。


1) 令牌不是会话 ID。它只是一个访问令牌。因此,如果他们丢失了令牌,他们只会重新获得它。 2)是的,内存中的意思是“变量”。我认为某些库还将 access_token 存储在 sessionStorage 中,因此您也可以这样做。但是,它增加了对任何恶意脚本的可见性。 3)我相信,解决原始问题没有通用且安全的解决方案。如果您在客户端上有东西,那么任何浏览器扩展程序或注入的脚本都可以访问该数据。
do silent sign-in when Access Token expires 是什么意思?
当用户在页面上停留很长时间而没有交互时,“访问令牌”就会过期。然后用户执行一些操作,API 以 401 响应。通常,客户端上会保存一个“刷新令牌”。在得到 401 作为响应后,UI 应该使用“刷新令牌”刷新“访问令牌”。如果没有“刷新令牌”,则 UI 可以简单地重新验证用户并获取新的“访问令牌”。
@VladimirSerykh但是“用户界面如何简单地重新验证用户”?如果它要求用户输入密码,那不是“沉默”;如果它使用其他令牌,它与刷新令牌有何不同,它存储在哪里?
@VladimirSerykh“正常方式”是什么意思?您仍然没有解释如何“默默地”获得任何东西!
y
yaches

您可以将令牌、访问和刷新都存储为 cookie。但是刷新令牌必须有特殊的路径(例如/refresh)。因此,刷新令牌将仅发送给 /refresh url 的请求,而不是像访问令牌这样的每个请求。


这是为什么?您不能在每个请求中发送刷新令牌,类似于基于会话的身份验证中的 remember.me cookie 吗?
@BartoszPopiela,因为刷新令牌应该在刷新时轮换。因此,如果您将它包含在每个请求中,您也会在每个请求中获得一个新令牌。如果您不在刷新时轮换令牌,这意味着长期令牌被盗的机会增加。
@s.meijer 您可以在每个请求中发送刷新令牌,但仅在访问令牌过期时才使用它,然后才创建新的访问令牌并轮换刷新令牌。
好的,根据 this 的回答,刷新令牌应该只与授权服务器交换以降低泄漏的风险,因此只将其发送到 /refresh 端点是有意义的,即使资源服务器和授权服务器是相同。
@Chano cookie 仍然被设置,但是当你关闭所有隐身窗口时它们被清除,所以它不应该影响用户登录的能力。
W
Winter

将访问令牌存储在会话存储中并通过 Bearer access_token 授权标头将其发送到我的资源服务器。然后我可以使用 httpOnly cookie 作为刷新令牌。这有一个我能想到的缺点:a) 每次向资源服务器发出请求时,刷新令牌都会暴露给 CSRF。

您可以正确设置 CORS policy,以便只接受来自授权服务器的对 /refresh_token 的请求。

如果客户端和服务器由同一台机器提供服务,您可以在 Cookie 中将标志 sameSite 设置为 true 并包含 anti-CSRF 令牌。


J
Jose Miralles

如果您的身份验证提供程序实施刷新令牌轮换,您可以将它们存储在本地存储中。

但这意味着每次客户端刷新 JWT 时,您的身份验证提供程序都应该返回一个新的刷新令牌。如果再次尝试使用一个刷新令牌,它还应该有一种使后代刷新令牌无效的方法。

https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation


C
Community

OAuth 定义了四种授权类型:授权码、隐式、资源所有者密码凭证和客户端凭证。它还提供了一种扩展机制来定义额外的授权类型。

__ RFC 6749 - The OAuth 2.0 Authorization Framework

Authorization Code 进程本质上是为与安全客户端一起使用而设计的,例如。一个服务器,它被保护得足以容纳 Client Secret。如果您的客户端足够安全以保存该机密,只需将 Refresh Token 与您的 Client Secret 放在相同的安全存储中。

托管在 User-Agent (UA) 中的应用程序并非如此。对于这些,规范建议使用 Implicit 授权类型,它在 # 符号之后的片段中的 Redirection URI 之后显示 Access Token。鉴于您直接在 User-Agent 中接收令牌,它本质上是一种不安全的方法,除了遵循 User-Agent 的安全规则外,您无能为力。

您可以将应用程序的使用限制为特定的用户代理,但这很容易被篡改。您可以将您的令牌存储在 cookie 中,但如果 UA 不遵守通用安全规范,也可以访问该 cookie。如果代币由 UA 实施和提供,您可以将代币存储在本地存储中,但前提是它遵守规范。

这些隐式间接授权的关键是对 UA 的信任;否则,最安全的授权类型是授权代码,因为它需要在受控环境(应用程序的服务器)上安全可靠地存储秘密。

如果您别无选择,只能使用隐式调用,请勇敢地相信用户使用遵循安全协议的安全 UA;您不以任何方式对用户对 UA 的错误选择负责。


这曾经是最佳做法,但在 2019 年初更改了建议。本页解释了为什么developer.okta.com/blog/2019/05/01/…
J
Jan Garaj

您没有使用最好的身份验证架构。 SPA 是一个公共客户端,它无法安全地存储诸如客户端密码或刷新令牌之类的信息。您应该切换到 Implicit Flow,其中 refresh tokens are not used。但是可以使用 Silent Authentication(静默续订)代替。

我建议使用 OIDC certified library,其中已为 SPA 应用程序排序。我最喜欢的一个:https://github.com/damienbod/angular-auth-oidc-client


是的,我尝试走那条路,但是如果我询问如何使用获得的访问令牌来访问我自己的 REST 端点,我会以同样的 session 与 jwt 问题结束,所以在我看来这有点毫无意义。一方面它作为灵丹妙药出售,另一方面他们说它只有在你没有后端的情况下才有效。现在告诉我一个除了待办事项列表之外没有后端的应用程序,即使那样......
它也非常非常令人困惑。新的代码流并没有为我提供给定问题的真正解决方案(即如何访问我自己的 REST 资源)。不建议使用刷新令牌,尽管它确实适用于保护权衡。我敢打赌,大多数开发人员也不知道并将返回服务器会话,或者实施最简单的流程,无论他们提供的保护级别如何。
第三件事是他们将访问令牌存储在会话存储中,那么这对给定的论点有何影响……我真的很想理解,但那里的大多数论点只有一半意义。另一半只是巧妙地避开。但它并没有完全点击作为一个整体。
隐式流现在被认为是遗留的,它们推荐的替换是带有 PKCE 的身份验证代码,这让我们再次回到这个问题
甚至 autho 也要求 SPA 和公共客户端在客户端存储刷新令牌和 access_token 并启用 PKCE 和 refresh_token 轮换。 auth0.com/docs/libraries/auth0-react#getting-started 因此,我认为如果您使用 PKCE 身份验证,现在将令牌存储在客户端上可能是一种公认的策略。