Have angular-oauth2-oidc retrieve access token from other tabs
Asked Answered
N

3

7

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?

Nipa answered 16/5, 2018 at 11:54 Comment(0)
N
11

Note: some time after asking and answering my question, I created and started maintaining a sample usage of said library which includes the advice from this answer.


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.

Nipa answered 18/5, 2018 at 9:47 Comment(0)
S
1

There is an explanation, why it always goes to IdentityServer to clarify the current user, and it is the code that you've shown.

Every time you open a tab, your app is started, and the code above is executed. Now - all these oidc libraries, that support SPA'a and the Implicit flow, are storing the user data (access_token ... ) in the browser session storage. By opening a new tab, you have a new session.

My point here is - you need to do something, before trying to authenticate against Identity Server. I'm pointing to something like moving all the user info, from the Session storage, to the Local storage. Then the tabs, that are under the same application (respectively same origin), will have a shared Local storage.

So your flow in the app start, should be something like:

  1. Check local storage for user info
  2. If existing, setup the authentication service (including the silent refresh), but don't try to login. And move the data to session storage (not sure that this is needed, but I guess that the library will look for it there)
  3. If not - login, and then move the data from the session storage to the local one
  4. And of course, on the silent renew callback, you will have to update the values in the local storage (they should be updated by the library in the session).
  5. And last, but not least - on logout, you will have to clean up.

For me this, seems like a solution for you. Now - it is up to you, to decide is the overhead worth it.

PS: Just to clarify - I have not tried it. I can't guarantee that it will work, but following the order of the events, it should.

Stretcherbearer answered 16/5, 2018 at 15:21 Comment(1)
Thanks for your answer, it led me in the right direction!Nipa
B
1

in app module.ts

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

@NgModule({ .. })


providers: [
{   provide: OAuthStorage,
        useFactory: oAuthStorageFactory
    }
]

this config will save the token in local strage instead of saving it in default session.

Branen answered 24/3, 2022 at 12:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.