ChatGPT解决这个技术问题 Extra ChatGPT

Where to store the refresh token on the Client?

My SPA application uses the following architecture (source):

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

This assumes that my client application knows about the refresh token, because I need it to request a new access token if no user credentials (e.g. email/password) are present.

My question: Where do I store the refresh token in my client-side application? There are lots of questions/answers about this topic on SO, but regarding the refresh token the answer are not clear.

Access token and refresh token shouldn't be stored in the local/session storage, because they are not a place for any sensitive data. Hence I would store the access token in a httpOnly cookie (even though there is CSRF) and I need it for most of my requests to the Resource Server anyway.

But what about the refresh token? I cannot store it in a cookie, because (1) it would be send with every request to my Resource Server as well which makes it vulnerable to CSRF too and (2) it would send expose both access/refresh token with an identical attack vector.

There are three solutions I could think of:

1) Storing the refresh token in an in-memory JavaScript variable, which has two drawbacks:

a) It's vulnerable to XSS (but may be not as obvious as local/session storage

b) It looses the "session" if a user closes the browser tab

Especially the latter drawback makes will turn out as a bad UX.

2) 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 drawback that I can think of:

a) The refresh token is exposed to CSRF with every request made to the Resource Server.

3) Keep both tokens in httpOnly cookies which has the mentioned drawback that both tokens are exposed to the same attack vector.

Maybe there is another way or more than my mentioned drawbacks (please let me know), but in the end everything boils down to where do I keep my refresh token on the client-side? Is it httpOnly cookie or an in-memory JS variable? If it is the former, where do I put my access token then?

Would be super happy to get any clues about how to do this the best way from people who are familiar with the topic.

Provide more information. Is this a SPA or server-based application?
In this case, it is an SPA.
Would love to know what's the solution you've implemented? I'm currently using option 2, without worrying about CSRF. The refresh token is used and invalidated on every SSR. But this causes access tokens out of sync when the SPA is opened in more than one tab.

V
Vlad DX

You can store encrypted 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.


1) The token is not a session id. It's just a access token. So, if they loose the token, the would just re-obtain it. 2) Yes, in-memory means "variable". I think some libraries also store the access_token in sessionStorage, so you can do the same. However, it increases visibility to any malicious scripts. 3) I believe, there is no common and secure solution to solve the original problem. If you have something on client, then any browser extension or injected script can access that data.
what do you mean with do silent sign-in when Access Token expires?
When user stays on the page for quite long time without interaction, 'access token' expires. Then user performs some action and API responds with 401. Usually, there is a 'refresh token' which is kept on the client. And after having 401 as response, UI should refresh the 'access token' using the 'refresh token'. If there is no 'refresh token', then UI can simply re-authenticate user and obtain fresh 'access token'.
@VladimirSerykh But how does "the UI simply re-authenticate the user"? If it asks the user for their password, that's not "silent"; if it uses some other token, how is that different from a refresh token, and where is it stored?
@VladimirSerykh What do you mean "the normal way"? You're still not explaining how you can "silently" obtain anything!
y
yaches

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.


Why is that? Can't you send the refresh token in every request similarly to the remember.me cookie in the session based authentication?
@BartoszPopiela, because a refresh token should be rotated on refresh. So if you'd include it on every request, you'd get a new token in each request as well. If you don't rotate the token on refresh, that means an increased chance that the long-lived token gets stolen.
@s.meijer You can send the refresh token in every request but use it only if the access token expired and only then create a new access token and rotate the refresh token.
Ok, according to this answer the refresh token should be exchanged only with the authorization server to mitigate the risk of leakage, so it makes sense to send it only to the /refresh endpoint, even if a resource server and an authorization servers are the same.
@Chano The cookies still get set but they are cleared when you close all incognito windows, so it should not affect a user's ability to log in.
W
Winter

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 drawback that I can think of: 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.


J
Jose Miralles

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


C
Community

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.


This used to be best practice, but advice was changed early in 2019. This page explains why developer.okta.com/blog/2019/05/01/…
J
Jan Garaj

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


Yeah I try to go that route, but if I ask about how to use the obtained access token to access my own REST endpoints, I end back with the same problem of session vs jwt so it's sort of pointless in my view. On one hand it is sold as a silver bullet, on the other hand they say it only works if you have no backend. Now tell me one application that has no backend except for a todo list and even then...
Also it is very, very confusing. The new code flow doesn't offer me a real solution to given problem (i.e. how to access my own REST resources). The refresh token is not advised, although it does work with protection trade offs. My bet is that the majority of developers out there also doesn't know and will go back to server session, or implement the flow that is the easiest, regardless the level of protection that they're offering.
Third thing is they store access token in session storage, so how does that make any difference to given argument then... I really like to understand, but the majority of arguments out there only make half sense. The other half is just smartly evaded. But it doesn't quite click as a whole.
Implicit flows are now considered legacy, and their recommended replacement is auth code with PKCE, which leads us back to this question again
Even autho has an ask for SPA and public clients that store refresh token and access_token at the client side with PKCE and refresh_token rotation enabled. auth0.com/docs/libraries/auth0-react#getting-started So I think now it may be an accepted strategy to store tokens on the client if you go with PKCE authentication.