Skip to content

Automatic silent refresh fails with multiple tabs when localStorage is used #850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ahofman opened this issue Jun 1, 2020 · 11 comments · May be fixed by #1423
Open

Automatic silent refresh fails with multiple tabs when localStorage is used #850

ahofman opened this issue Jun 1, 2020 · 11 comments · May be fixed by #1423
Labels
bug For tagging faulty or unexpected behavior. investigation-needed Indication that the maintainer or involved community members may need to investigate more.

Comments

@ahofman
Copy link

ahofman commented Jun 1, 2020

Describe the bug
When an application configures the angular-oauth2-oidc client to use localStorage, the automatic silent refresh process for the code flow fails when multiple tabs are opened.

Stackblitz example
The issue can be trivially reproduced using the sample app, with the only required modification being to add the following to appModule.ts

export function storageFactory(): OAuthStorage {
  return localStorage;
}

and add the following provider in the module:

{ provide: OAuthStorage, useFactory: storageFactory },

To Reproduce
Steps to reproduce the behavior:

  1. Open the sample app in a new incognito tab in Chrome
  2. Click "Login with Code Flow" and sign in
  3. Open dev tools and observe the periodic console messages saying that the refresh token is being used
  4. Open the sample app in a new incognito tab
  5. Open dev tools in the new tab
  6. Check the consoles in both tabs, eventually an error will appear in one of them
  7. Observe error in the console: Error refreshing token

Note that the session checking kicks in and causes the token to refresh again successfully.
In applications where session checking is not configured, the refreshing does not recover.

Expected behavior
The refreshing of tokens should be thread safe such that when localStorage is used multiple tabs do not try to refresh using the same refresh token at the same time.

@jeroenheijmans jeroenheijmans added bug For tagging faulty or unexpected behavior. investigation-needed Indication that the maintainer or involved community members may need to investigate more. labels Jun 1, 2020
@jeroenheijmans
Copy link
Collaborator

Thx for reporting this issue. I've encountered similar problems in the past, and making this work a bit better seems like a great idea.

Would you have any suggestions as to how to improve this? I don't feel there's a simple solution available, especially since from the library's point of view it's not known beforehand what OAuthStorage will be used, and what it's features would be...

@xuanswe
Copy link

xuanswe commented Aug 11, 2020

This is a difficult problem. The main problem is to sync state between tabs.

Maybe, before sending request to refresh the token, it should double check if the newer refresh token is existing and reschedule to use the new refresh token. So only the tab, which uses the current refresh token the first time will win, the other tabs will need to reschedule when it see that there's already a new refresh token.

@xuanswe
Copy link

xuanswe commented Aug 11, 2020

The same error happens when use sessionStorage

@KirschbaumP
Copy link

I have a very similar problem. Maybe it is the same:

To Reproduce

  1. Open the sample app in a new incognito tab in Chrome
  2. Click "Login with Code Flow" and sign in
  3. Open dev tools and observe the periodic console messages saying that the refresh token is being used
  4. Reload the page
  5. Open dev tools and observe the periodic console messages saying that the refresh token is being used and the error occures

The error Message is than allways "login_required"

@jeroenheijmans
Copy link
Collaborator

new incognito tab in Chrome
...
The error Message is than allways "login_required"

@KirschbaumP This is I think a different symptom than this issue describes (a race condition), and most likely due to third party cookie problems? I presume your IDS lives on another domain than your SPA when you encounter this? You could check my blogpost https://infi.nl/nieuws/spa-necromancy/ for a problem description, and some potential solutions/workarounds.

@myushchenko
Copy link

In order to fix issue with localStorage I suggest to use new Lock API https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API and refresh token by hand

locks.request('my_resource', async () => {
  await this.oAuthService.refreshToken();
});

@voegtlel
Copy link

If you want to keep automatic token refresh, I found the following patch after playing around a while:

    // Initializing the service...

    let lastUpdateToken: string | undefined;

    if (oauthService.hasValidAccessToken()) {
      lastUpdateToken = oauthService.getAccessToken();
    }

    oauthService.loadDiscoveryDocumentAndTryLogin();
    oauthService.setupAutomaticSilentRefresh();

    // Workaround patch: Synchronize silent refresh over multiple tabs/windows
    const originalSilentRefresh = oauthService.silentRefresh.bind(oauthService);
    oauthService.silentRefresh = async (params: any = {}, noPrompt = true): Promise<OAuthEvent> => {
      return await (navigator as any).locks.request('MyUniqueName.code', async () => {
        if (lastUpdateToken !== oauthService.getAccessToken()) {
          // Was already updated in another tab/window
          console.log('Updated token from other window');
          (oauthService as any).eventsSubject.next(new OAuthSuccessEvent('token_received'));
          (oauthService as any).eventsSubject.next(new OAuthSuccessEvent('token_refreshed'));
          const e = new OAuthSuccessEvent('silently_refreshed');
          (oauthService as any).eventsSubject.next(e);
          lastUpdateToken = oauthService.getAccessToken();
          return e;
        } else {
          // Simply run the original update
          const e = await originalSilentRefresh(params, noPrompt);
          lastUpdateToken = oauthService.getAccessToken();
          return e;
        }
      });
    };

As suggested by @myushchenko , it's based on the Web Lock API. It can be polyfilled usind web-locks-polyfill. Maybe this finds integration into the main repo? I believe there should be more configuration that just the patch.

@dofamine
Copy link

any updates?

@dofamine
Copy link

I propose to use a unique key per opened tab for storing 'nonce' in any storage, so that other opened tabs cant use it twice!

@ekhtiari
Copy link

any update?

@andpii
Copy link

andpii commented May 22, 2023

    let lastUpdatedToken: string | undefined;

    if (this.oauthService.hasValidAccessToken()) {
      lastUpdatedToken = this.oauthService.getAccessToken();
    }

    const originalSilentRefresh = this.oauthService.refreshToken.bind(this.oauthService);
    this.oauthService.refreshToken = (): Promise<TokenResponse> => {
      return navigator.locks.request(`refresh_tokens_${location.origin}`, () => {
        if (!!lastUpdatedToken && lastUpdatedToken !== this.oauthService.getAccessToken()) {
          (this.oauthService as any).eventsSubject.next(new OAuthSuccessEvent('token_received'));
          (this.oauthService as any).eventsSubject.next(new OAuthSuccessEvent('token_refreshed'));
          lastUpdatedToken = this.oauthService.getAccessToken();
          return;
        }

        return originalSilentRefresh().then((resp) => lastUpdatedToken = resp.access_token);
      });
    }

Perhaps someone still needs an implementation of @voegtlel's answer for Code Flow.

@chdanielmueller chdanielmueller linked a pull request Jul 18, 2024 that will close this issue
alep85 added a commit to Alfresco/alfresco-ng2-components that referenced this issue Jul 23, 2024
DenysVuika pushed a commit to Alfresco/alfresco-ng2-components that referenced this issue Jul 23, 2024
* Fix refresh token error with multiple opened tabs (kwnown angular-oauth2-oidc issue => manfredsteyer/angular-oauth2-oidc#850)

* fix typo

* AAE-24081 test silent refresh and token refresh are called when automatic silent refresh is setup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug For tagging faulty or unexpected behavior. investigation-needed Indication that the maintainer or involved community members may need to investigate more.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants