Have angular-oauth2-oidc retrieve access token from other tabs

Jeroen picture Jeroen · May 16, 2018 · Viewed 8.4k times · Source

I'm using the angular-oauth2-oidc library in combination with the Implicit Flow (with an IdentityServer4 server). I've successfully set up the Silent Refresh suggestion from the docs.

Here's how I bootstrap things in a wrapper service:

private userSubject = new Subject<User>();

constructor(private config: ConfigService, private oAuthService: OAuthService)
{ }

// Called on app load:
configure(): void {
    const config: AuthConfig = {
      issuer: this.config.getIdentityUrl(),
      logoutUrl: this.config.getIdentityUrl() + '/connect/endsession',
      redirectUri: window.location.origin + '/',
      silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html',
      clientId: 'my_client_id',
      scope: 'openid profile my_api',
      sessionChecksEnabled: true,
    };

    this.oAuthService.configure(config);
    this.oAuthService.tokenValidationHandler = new JwksValidationHandler();

    this.oAuthService
      .loadDiscoveryDocumentAndLogin()
      .then((_) => this.loadUserProfile());

    this.oAuthService.setupAutomaticSilentRefresh();
}

private loadUserProfile() {
  this.oAuthService.loadUserProfile()
    .then((userProfile) => {
      const user = new User(userProfile['name']);
      this.userSubject.next(user);
    });
}

However, if I open the application in a new tab, the user also gets redirected to the IdentityServer (and immediately back to my app).

My question: can I get the library to retrieve existing access token (and optionally user info) from other tabs of the same origin, to prevent the redirects? (Preferred because it doesn't require Ajax calls.)

Alternatively, is there an easy way to try and use the Silent Refresh mechanism before we'd send someone to the IdentityServer?

Answer

Jeroen picture Jeroen · May 18, 2018

First up: I somehow got the idea that sessionStorage was right for tokens and that localStorage should always be avoided. But that was from another project where more powerful (refresh) tokens were involved, and with implicit flow I only have shortlived access tokens so localStorage is also not that much more unsafe than sessionStorage. In the end: assess attack vectors for your own specific situation: "it depends".

I did not mention that I had that idea, and the other answer helpfully made me rethink things and consider using localStorage. This was in fact a good solution.

Turns out, the library has built in support for using localStorage as the backing store for tokens and other data. At first though I was trying:

// This doesn't work properly though, read on...
this.oAuthService.setStorage(localStorage);

But that way of bootstrapping things didn't work for my case, see issue 321 on the libraries GitHub issues list for my log on that. Repeating the solution (or workaround?) from that thread, I solved things by doing this in the app module's providers:

{ provide: OAuthStorage, useValue: localStorage },

Now the library will use localStorage properly and new tabs (and even new windows) will automatically pick it up.


As a footnote, if you don't want to use localStorage for example for security reasons, you could also provide your own storage as long as it implements the OAuthStorage interface. Your own implementation could then use any of the available inter-tab communication techniques to "ask" the data from other tabs, falling back to sessionStorage if needed.