Our implementation for authentication works like this

  1. User provides username/password to /login API
  2. API returns access token and refresh token in payload
  3. We store the access token and refresh token in session storage
  4. After a period of time, the access token expires and the server throws expiry error, on which we hit the /refresh API, which returns a new refresh and access token again to be stored in session storage.

The issue

  1. When a user duplicates a chrome tab, the session storage is also duplicated (the access token and refresh token) is same now in another tab.
  2. When one of the tab has an expired token, it will hit the /refresh API and get new refresh token and access token. While the back-end will invalidate the previous pair of tokens.
  3. Now one of the tab will have invalid token which causes all api calls to fail for that browser.

Proposed Solution

  1. Use httpOnly cookies to store the access and refresh token. Which will solve the issue for us.
  2. Use broadcast channel api to sync the 2 duplicate sessions

Even after a lot of research I'm unable to decide which solution would be more secure since we already have covered CSRF and XSS attack vectors as much as we can.

  • 2
    Why was the initial idea to use session storage? Commented May 16 at 18:06
  • It was a bad decision in hindsight now.
    – Vin Xi
    Commented May 16 at 18:09

2 Answers 2


Security-wise, as long as you've got strong anti-CSRF protection, cookies (with Secure and HttpOnly, of course) are the way to go. They're simpler (less room for error), scale to arbitrary numbers of documents (tabs, other windows, child windows, frames, etc.), and won't get leaked in the event of XSS (though an attacker can still remote-control the session so long as the victim has the page open).

However, the "strong anti-CSRF protection" part matters a lot. There's a lot of ways to do CSRF protection, with varying degrees of security, effort, and compatibility. Sometimes people also try things that don't actually reliably work, or rely on protections they think are more universal than in reality (e.g. I once saw a site that rejected requests with the Origin header on the grounds that all cross-origin requests - even non-CORS ones like form submissions - have that, which might have been true of new versions of Chrome specifically but was by no means universally true back then, and might not even be universally true today). Additionally, while CSRF is the primary security risk for cookies, you should make sure they've got all the right flags (Secure is in theory redundant with HSTS but use it anyhow), don't allow domains including subdomains you don't need, etc.

Side notes:

the back-end will invalidate the previous pair of tokens

This surprises me, since the whole reason to use refresh tokens is that access tokens are usually JWTs, and completely stateless, which makes revocation/invalidation extremely inconvenient. They thus must have extremely short lifetimes so that a compromised token becomes useless as soon as possible. If you can reliably invalidate access tokens prior to their expiry, why bother with refresh tokens at all?

Also, problems like this are one of the reasons that making refresh tokens single-use is questionable from a UX perspective. Another is anybody on a poor connection where a refresh might reach the server but not get back to the client, or when many requests may be in flight at the same time due to latency. There's no good way to fix that while preserving the ability to detect compromised tokens being reused, though asking clients to immediately ping the auth service after refreshing (and not invaliding the old token[s] until that ping shows up with the new one[s]) is an option.


The first option (storing the tokens in HttpOnly cookies) is slightly more secure.

The Web Storage API (consisting of localStorage and sessionStorage) gives any script that runs in a particular origin unrestricted access to all data. So if there's an XSS vulnerability on your website, or if an attacker manages to inject JavaScript code through one of the scripts you're including (like a third-party library), then the tokens can immediately be read and exfiltrated. No feature of the Web Storage API prevents this.

In comparison, cookies with the HttpOnly attribute generally cannot be accessed with JavaScript, so this particular attack doesn't work. Note, however, that there are still several caveats and limitations which you need to consider.

  • While an attacker cannot read the tokens, they can still use them if they've found an XSS vulnerability. Whenever they make a request to the protected API, the token is automatically included. Depending on what exactly the attacker wants to achieve, this limited access may be perfectly sufficient, in which case HttpOnly makes no difference.
  • The HttpOnly attribute can be defeated with Cross-Site Tracing.
  • You also have to be very careful not to include the token anywhere in a response, because this again can allow an attacker to obtain the token despite the HttpOnly attribute.

You must log in to answer this question.

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