1

I am trying to figure out how to handle access and refresh tokens securely on the front end in a SPA. After doing a lot of reading, I still have questions/doubts about the proposed solutions I found on the internet.

Basically, the internet say that the access token should be "in memory" only (not localStorage or sessionStorage). There is a suggestion to keep the access token in a JavaScript closure. This I understand completely. As for the refresh token, the internet says that it should be stored as a HttpOnly and Secure cookie. This way when access_token returns an authentication error, I can post to the refresh token endpoint and get a new access_token + refresh_token. I understand that this type of setup minimizes the chance of XSS stealing the tokens. However, in an application where the resources and the authentication are different API end points on the same exact domain, the refresh token is going to be added to every API call. Since the refresh tokens are long living and are going to be sent with every API request, isn't that a security issue also?

6
  • 1
    if the authentication and resource endpoints are on the same server, you may not need refresh tokens at all. Just re-authenticate the user and provide a new access token at expiration. "In Memory" storage does not protect you from XSS. Only an HTTP-Only cookie would do that. (Though even then, XSS might still be able to prompt the user to enter username/password and send it somewhere else...) The same-domain security issue would probably be CSRF, so be sure to use anti-CSRF measures. This would also protect against CRIME/BREACH. Commented Mar 7 at 19:47
  • @browsermator What's the difference between what you're suggesting, and just having a long-lived access token (which, assuming the access token is a JWT, is insecure as there's no good way to revoke or prematurely invalidate them)?
    – CBHacking
    Commented Mar 7 at 21:21
  • Not sure really... I'm thinking the whole point of the refresh token is that it's only exchanged with the authentication server. If that's the same server as the resource server, how is it helping anything? I suppose it allows the access token to be treated less securely than the refresh token? Still if there are revocations happening on the refresh token it's requiring a lookup against revocation table which seems to defeat the purpose of using JWTs in the first place. Maybe just use the access id? Commented Mar 7 at 21:34
  • 1
    @browsermator That's part of the point of access/refresh token patterns when used with OAuth, but there are advantages to the pattern for same-server backends too. In particular, the advantage of JWTs is that they're stateless - no need to look up the token on every request - but the downside is that they're thus irrevocable and so need to be short-lived, which is a bad user experience. Refresh tokens fill in the gap; they are stateful (using one requires a lookup, but they can be revoked) and long-lived, but only used on requests to the refresh endpoint, ensuring minimal perf impact.
    – CBHacking
    Commented Mar 7 at 21:51
  • 1
    To be clear, you can just use a long-lived random token rather than a JWT, if you aren't using separate auth and resource servers (technically you can even if you do separate them, but the perf impact of querying the auth server in the backend may be significant). Random tokens need to be stored in the server (typically in a DB), of course, with at minimum their identity or authz data but often more, and must be looked up on every authenticated request, which impacts scalability. But they are in some ways more secure than JWTs, and this pattern is old and widely used.
    – CBHacking
    Commented Mar 7 at 22:02

1 Answer 1

2

Your design is, broadly speaking, good. While you shouldn't count on keeping the access token in a JS closure being sufficient to avoid its exposure through XSS - if nothing else, an attacker with XSS execution can always just hit the refresh endpoint to directly get a new access token for that user - it shouldn't hurt anything. It does mean that different tabs will have different access tokens, and that refreshing the page will require refreshing the access token, but those are common user inconveniences of SPAs in general (speaking purely as a user, please at least make Back and Forward work correctly!). You could store the access token in session storage - it makes no real difference in security - and then it would persist across refreshes and tabs, but you'd still have the refresh logic needed.

You could also improve user experience a little by setting a timeout and refreshing the access token shortly before it expires, ensuring that the user doesn't have to wait for an extra round trip every few minutes when they request data (this delay should be very small, but surprisingly short delays still impact UX). You'd still need to implement the "refresh if the token is expired/invalid" since preemptive refresh won't always work (e.g. if the user put the computer to sleep). This has no security impact at all; it's purely a UX thing, which I'm mentioning because so many SPAs end up being bad UX.

On to your questions:

However, in an application where the resources and the authentication are different API end points on the same exact domain, the refresh token is going to be added to every API call.

This is the use case for the relatively uncommon Path cookie parameter. While it has no security impact - if you don't fully trust all your servers, DO NOT put them on the same domain, there's no way to make that secure - you can avoid sending the cookie to endpoints that don't use it.

With that said, I wouldn't worry. Refresh tokens are shorter than JWTs or many other cookies - 128 bits of randomness, even hex-encoded rather than base64, is only 32 bytes for the cookie value - and most sites have far more cookie bloat than that. So while it makes sense to use the Path parameter here, it's hardly a disaster if you don't.

Since the refresh tokens are long living and are going to be sent with every API request, isn't that a security issue also?

Not really. We already talked about why you don't need to send it on every request, and also why it shouldn't matter even if you do. The "long lived" thing is just inherent in how refresh tokens work, and it's not unique to them; random session tokens, API keys, and of course credentials like passwords themselves are all also long-lived. There are some best practices for slight improvements in security, though:

  • Rotate the refresh token every time it is used. That is, validate the token, and then generate both a new access token and a new refresh token and return them to the client (presumably in the body and via Set-Cookie header respectively), and store the new refresh token on the server (replacing the old one). This can cause problems sometimes - for example, if the user has two tabs open to your site, and they try to refresh simultaneously, you have a race condition where both might send the refresh old token but only one of them receive a new access token - but there are ways around that (e.g. don't immediately invalidate the old refresh token; keep it around for a few seconds).
  • Hash the refresh token before storing it on the server, and when validating it from the client. Unlike passwords, you can use a single-round unsalted fast hash (e.g. a member of the SHA2 or SHA3 families) here; assuming your refresh tokens are generated with an appropriate level of entropy, there's no need to worry about guessing, brute-forcing, or collisions between tokens. Hashing prevents exposing other peoples' refresh tokens in the event of an information disclosure breach (e.g. read-only SQLi, stolen DB backup, snooping admin), and also inherently prevents string comparison timing attacks.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .