我的 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 变量?如果是前者,那我应该把我的访问令牌放在哪里?
非常乐意从熟悉该主题的人那里获得有关如何以最佳方式做到这一点的任何线索。
您可以将加密令牌安全地存储在 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 的现有库比自行构建要好。
您可以将令牌、访问和刷新都存储为 cookie。但是刷新令牌必须有特殊的路径(例如/refresh)。因此,刷新令牌将仅发送给 /refresh url 的请求,而不是像访问令牌这样的每个请求。
将访问令牌存储在会话存储中并通过 Bearer access_token 授权标头将其发送到我的资源服务器。然后我可以使用 httpOnly cookie 作为刷新令牌。这有一个我能想到的缺点:a) 每次向资源服务器发出请求时,刷新令牌都会暴露给 CSRF。
您可以正确设置 CORS policy
,以便只接受来自授权服务器的对 /refresh_token
的请求。
如果客户端和服务器由同一台机器提供服务,您可以在 Cookie
中将标志 sameSite
设置为 true 并包含 anti-CSRF
令牌。
如果您的身份验证提供程序实施刷新令牌轮换,您可以将它们存储在本地存储中。
但这意味着每次客户端刷新 JWT 时,您的身份验证提供程序都应该返回一个新的刷新令牌。如果再次尝试使用一个刷新令牌,它还应该有一种使后代刷新令牌无效的方法。
https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation
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 的错误选择负责。
您没有使用最好的身份验证架构。 SPA 是一个公共客户端,它无法安全地存储诸如客户端密码或刷新令牌之类的信息。您应该切换到 Implicit Flow,其中 refresh tokens are not used。但是可以使用 Silent Authentication(静默续订)代替。
我建议使用 OIDC certified library,其中已为 SPA 应用程序排序。我最喜欢的一个:https://github.com/damienbod/angular-auth-oidc-client
access_token
存储在 sessionStorage 中,因此您也可以这样做。但是,它增加了对任何恶意脚本的可见性。 3)我相信,解决原始问题没有通用且安全的解决方案。如果您在客户端上有东西,那么任何浏览器扩展程序或注入的脚本都可以访问该数据。do silent sign-in when Access Token expires
是什么意思?401
响应。通常,客户端上会保存一个“刷新令牌”。在得到 401 作为响应后,UI 应该使用“刷新令牌”刷新“访问令牌”。如果没有“刷新令牌”,则 UI 可以简单地重新验证用户并获取新的“访问令牌”。