Use angular-oauth2-oidc and "silent refresh" without white listing silent-refresh.html

Jeroen picture Jeroen · Apr 30, 2018 · Viewed 7.7k times · Source

I'm trying to use the angular-oauth2-oidc Silent Refresh implementation in combination with the implicit flow configured in an IdentityServer4 server. I've got a proof of concept working inside an ng new ng-and-ids4 --minimal application with this component:

import { Component } from '@angular/core';
import { AuthConfig, OAuthService, JwksValidationHandler, OAuthErrorEvent } from 'angular-oauth2-oidc';

@Component({
  selector: 'app-root',
  template: `<h1>Welcome!</h1>
    <p>
      <button (click)='login()'>login</button>
      <button (click)='logout()'>logout</button>
      <button (click)='refresh()'>refresh</button>
    </p>
    <h2>Your Claims:</h2><pre>{{claims | json}}</pre>
    <h2>Your access token:</h2><p><code>{{accessToken}}</code></p>`,
  styles: []
})
export class AppComponent {
  constructor(public oauthService: OAuthService) {
    this.oauthService.configure({
      issuer: 'http://localhost:5000',
      redirectUri: window.location.origin + '/',
      silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html',
      clientId: 'my-spa',
      scope: 'openid profile',
      silentRefreshTimeout: 5000, // For faster testing
      sessionChecksEnabled: true,
    });

    this.oauthService.tokenValidationHandler = new JwksValidationHandler();

    this.oauthService.loadDiscoveryDocumentAndLogin();
    this.oauthService.setupAutomaticSilentRefresh();
    this.oauthService.events.subscribe(e => { 
      if (e instanceof OAuthErrorEvent) { console.error(e); } 
      else { console.warn(e) } 
    });
  }

  public get claims() { return this.oauthService.getIdentityClaims(); }
  public get accessToken() { return this.oauthService.getAccessToken(); }

  login() { this.oauthService.initImplicitFlow(); }
  logout() { this.oauthService.logOut(); }
  refresh() { this.oauthService.silentRefresh(); }
}

On the IDServer4 side I'm configuring my client like this:

new Client
{
    ClientId = "my-spa",
    AllowedGrantTypes = GrantTypes.Implicit,
    AllowAccessTokensViaBrowser = true,
    AccessTokenLifetime = 30, // Seconds (for testing purposes)
    RedirectUris = { "http://localhost:4200/", "http://localhost:4200/silent-refresh.html" },
    PostLogoutRedirectUris = { "http://localhost:4200/" },
    AllowedCorsOrigins = { "http://localhost:4200" },
    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
    }
}

As you can see, on the server I've whitelisted "http://localhost:4200/silent-refresh.html" in the Client's RedirectUris property.

My question is whether you can configure angular-oauth2-oidc in a way that it doesn't require me to whitelist the silent-refresh.html page?

The reason I ask is because I want to use this Silent Refresh approach also in situations where it might not be so easy to change the IdentityServer side. Also, looking at Damien Bod's example of Silent Refresh in an Angular application I feel it should somehow be possible, because there such a whitelisting is not mentioned.

PS. If I don't include the extra option for the RedirectUris, then I get Invalid redirect_uri: http://localhost:4200/silent-refresh.html on the server (logs) and a silent_refresh_timeout OAuthErrorEvent in the client side library.

Answer

Jeroen picture Jeroen · Jul 23, 2018

After working some more with the library, I think the answer is that you can't easily do it.

If you have to do it, you'd need to tweak the index.html that serves the Angular app so that it bootstraps differently. Basically, the silent refresh would load that file instead of the silent-refresh.html file, so the index will have to have 2 moduses: one for the regular app loading, and another one that effectively does this:

parent.postMessage(location.hash, location.origin);

Because that's all the silent-refresh.html does. This can prove a bit difficult though, because it would require hooking into the generation of index.html when the CLI creates a production build.

So even though it's technically possible, it's not very pragmatic to do so.