OAuth 2.0 Refresh Token multiple Tabs
Asked Answered
D

5

13

When using the OAuth 2.0 JWT Refresh token implementation I came across the issue that it's really difficult to implement a solid Refresh Strategy on the Web Browser Client Side. Multiple Tabs can lead to a racing condition with the requests.

The RFC does not explicitly mention to have the Refresh Token on the Server side only valid for one (the first) request, but I figured it was a good idea to invalidate the Refresh tokens when they are used.

There are already multiple "solutions" on stack overflow but none of them seem to be straight forward.

One solution is to add Jitter to the requests and synchronize requests over the Local storage.

If I understand correctly you would put a variable into the Local storage when the request is started other tabs check if this variable is set and then don't start the refresh? Do you know an example implementation of this? Maybe in React?

Dense answered 15/5, 2020 at 8:30 Comment(1)
have you ever found a proper answer to this? I'm using the sessionStorage and everytime I open a new tab, I clear the cloned one from the previous tab leading to have new token for each tab and hence, different expiration_time to avoid the race condition but curious what to do to prevent this and use the localStorage strategy and share 1 token across the tabs.Galitea
T
3

The answer above does not really answer the question:

  • Using the OIDC client library does not solve this problem, in fact it does not even use refresh tokens as far as I know.

  • Storing tokens in memory or session storage does not solve the problem but will generate even more, see below.

  • Using the AS's session cookie is not feasable in some cases. Usually this is a cross-domain cookie which cannot be used reliably on other sites. This concept is called "silent renewal" and requires the use of a cross-domain cookie in an iframe to refresh tokens (using the AS session). This concept sounds nice, but with browsers and users blocking more and more cross-domain tracking mechanisms this is really dangerous to use: in most cases, blocked cookies cannot be detected (which leads to sudden logouts after some seconds, especially when using the OIDC Session Management mechanisms. Redirecting through the AS when refreshing tokens is also not an option, as in many cases, tokens are JWT and only valid for some minutes, and breaking the experience by redirecting away from the app every few minutes is not an option.

With PKCE and Authorization Code flow in the browser it is fine to use and store refresh tokens, but as the original poster said, care has to be taken when refreshing (especially when refresh tokens can only be used once, which is desired in browser environments!)...

Tybalt answered 4/2, 2021 at 22:32 Comment(0)
M
2

If you are using a cookieless SPA the most common solution is to avoid refresh tokens and do this:

  • Use the OIDC client library
  • Store tokens in either memory or session storage (local storage adds lots of browser issues)
  • Use the Authorization Server's session cookie (which is shared between browser tabs) for renewal via a hidden iframe

COOKIELESS SPA RESOURCES

Here is some stuff of mine that may help, if I'm understanding your setup:

Musaceous answered 15/5, 2020 at 20:43 Comment(1)
Why does this answer have a '-1' ?Horbal
C
1

Take a look at Navigator.locks, which can be used to get a lock across different contexts, and then perform a request when you acquire it:

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/locks

Then the code could look as follows:

window.navigator.locks.request(
    'unique_name_for_refresh_checking', 
    async () => {
        if (needRefresh) {
            performRefresh()
        }
    },
)

This will make only the tab that has acquired the lock actually perform the refresh request.

You do have to make sure that the response from this request is propagated to other tabs by some method. For example: writing to local storage with the new tokens and listening for this in other tabs.

Chrotoem answered 29/3, 2023 at 16:11 Comment(0)
I
0

I use the page Visibility API https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API for this purpose. When tab is inactive I cancel renewal subscription and setup for renewal when tab is active again.

My code for Angular is:

  constructor(
    private router: Router,
    private http: HttpClient,
    private tokenService: TokenService,
    @Inject(DOCUMENT) private readonly documentRef: Document,
    ) {
    this.getToken(); // From local Browser store
    this.loggedIn = this.isAuthenticated();
    this.status = new BehaviorSubject<boolean>(this.loggedIn);
    this.documentRef.addEventListener("visibilitychange", () => {
      console.log(document.hidden, document.visibilityState);
      if (document.hidden) {
        this.cancelRenewal();
      } else {
        this.getToken();
        this.loggedIn = this.isAuthenticated();
        this.status.next(this.loggedIn);
        this.scheduleRenewal();
      }
    }, false);
  }
Ignoramus answered 17/2, 2022 at 8:53 Comment(1)
Tbh, this can be risky since the user may end up choosing a different tab such as Youtube and hence you will prevent yourself from silently refreshing the token in all the SPA tabs and would end up getting error.Galitea
T
0

I made a solution for our internal project several months ago and saved the business-insensitive version as https://github.com/hey-web/share-token-among-tags.

The core approach of it as follows:

  1. all active tabs generate their id in number format.
  2. each tab tries to propose itself as the main tab if its id is larger than the main id saved in the Localstorage if any. If no main id is saved, the first tab that proposes would be the main tab.
  3. the main tab is responsible to refresh the access token and save back the new access token into the Localstorage.
  4. other no-main tab listen to the update event of Localstorage and save the token if it's updated. Read more about the event
  5. each tab fires a loop-checking function to ensure that the main tab is still active. if the main is dead, all the tabs propose again.

Hope this would help.

Tarrel answered 25/11, 2022 at 5:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.