在客户端上存储刷新令牌的位置?

我的 SPA 应用程序使用以下体系结构(来源) :

enter image description here

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

我的问题: 我在客户端应用程序中将刷新令牌存储在哪里?关于这个主题在 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 变量?如果是前者,那么我应该把我的访问令牌放在哪里?

如果能从熟悉这个话题的人那里得到任何关于如何做到这一点的最佳方法的线索,我会非常高兴。

89705 次浏览

You are not using the best authentication architecture. The SPA is a public client and it is unable to securely store information such as a client secret or refresh token. You should switch to Implicit Flow, where refresh tokens are not used. But Silent Authentication (silent renewal) is available instead.

I recommend to use OIDC certified library, where is all already sorted for SPA apps. My favorite one: https://github.com/damienbod/angular-auth-oidc-client

You can store tokens securely in HttpOnly cookies.

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

If you worry about long-living Refresh Token. You can skip storing it and not use it at all. Just keep Access Token in memory and do silent sign-in when Access Token expires.

Don't use Implicit flow because it's obsolete.

The most secure way of authentication for SPA is Authorization Code with PKCE.

In general, it's better to use existing libraries based on oidc-client than building something on your own.

OAuth defines four grant types: authorization code, implicit, resource owner password credentials, and client credentials. It also provides an extension mechanism for defining additional grant types.

__ RFC 6749 - The OAuth 2.0 Authorization Framework


The Authorization Code process is inherently designed to be used with a secure client, eg. a server, that is guarded enough to hold the Client Secret within. If your client is secure enough to hold that secret, just put the Refresh Token in the same secure storage as your Client Secret.

This is not the case with applications that are hosted in User-Agent (UA). For those, the specification suggests using Implicit grant type which presents the Access Token after the Redirection URI in a fragment after # sign. Given that you are receiving the token in the User-Agent directly, it is inherently an insecure method and there is nothing you can do about it except following that User-Agent's security rules.

You may restrict the usage of your application to specific User-Agents but that can easily be tampered with. You may store your tokens in a cookie, but that also can be accessed if the UA does not respect common security norms. You can store your tokens in local storage if it is implemented and provided by the UA, yet again if it respects the norms.

The key to these implicit indirect authorizations is the trust in UA; otherwise, the safest grant type is authorization code because it requires a safely and securely stored secret on a controlled environment (application's server).

If you have no choice but using the implicit call, just go with your guts and trust the user uses a safe UA that follows security protocols; any way you are not responsible for user's poor choice of UA.

You can store both tokens, access and refresh, as cookie. But refresh token must have special path (e.g. /refresh). So refresh token will be sent only for request to /refresh url, not for every request like access token.

Storing the access token in session storage and sending it via a Bearer access_token authorization header to my resource server. Then I can use httpOnly cookies for the refresh token. This has one 我能想到的缺点: a) The refresh token is exposed to CSRF with every request made to the Resource Server.

You can set up the CORS policy correctly so that the requests to /refresh_token are accepted only from authorized servers.

If the client and server are served from the same machine, you can set the flag sameSite as true in the Cookie and include an anti-CSRF token.

If your Auth provider implements refresh token rotation, you can store them in local storage.

But this means that your Auth provider should return a new refresh token every time that the client refreshes a JWT. And it should also have a way of invalidating descendant refresh tokens if one refresh token is attempted to be used a second time.

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