5

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.

Jeroen
  • 60,696
  • 40
  • 206
  • 339
  • If you remove it from the Client RedirectUri's you get an "unaothorized client" error? – m3n7alsnak3 May 01 '18 at 14:44
  • @m3n7alsnak3 No, I get `Invalid redirect_uri: http://localhost:4200/silent-refresh.html` then on the server (logs) and a `silent_refresh_timeout` `OAuthErrorEvent` in the client side library. – Jeroen May 03 '18 at 11:51
  • Got it. Honestly I think that Damienbod is using something on top of `angular-oauth2-oidc` and not the OOTB silent refresh (check his `export class OidcSecuritySilentRenew` class). I guess that you can use the default url (http://localhost:4200 in your case) as a silent renew redirect url, and implement some logic on the entry point of your app, but for me it is not the clearest approach. Why are you saying that you won't be able to make changes on IDS side? How are you going to whitelist the default URL at this point? – m3n7alsnak3 May 03 '18 at 15:17
  • @m3n7alsnak3 On my current project I own the IDServer codebase and have now made the necessary changes. On another project though I would like this same aapeoach, but there a change request to the IDServer might take weeks, if it even gets through the politics. That's why I asked. – Jeroen May 04 '18 at 05:37
  • If adding a new client to the identity server requires a change request (which for me means change in the code base), then you have far bigger problem than the silent redirect url – m3n7alsnak3 May 04 '18 at 06:25
  • @Jeroen did you ever solve this? I'm having the same issue. – Ryan Buening Jun 05 '18 at 18:23
  • 1
    @RyanBuening Nope. I'm still curious about an answer (a) because the other library seems to be able to do it and (b) because I might end up in a similar scenario in the future (regardless of m3n7alsnak3 being right that this is in fact a -possibly political- problem) . But in my current project it turned out to be easier to get the white list changed than to carry on solving this on the front-end. – Jeroen Jun 05 '18 at 18:49
  • 1
    @RyanBuening After playing some more with the library I do think it should be possible, because the iframe html doesn't do much, and I suppose you could also hack that into your app's index.html. But I haven't tried it. – Jeroen Jun 05 '18 at 18:50
  • 1
    ...and now you're the most active member of this library's community :-) @Jeroen – AD8 Jun 24 '20 at 14:07

1 Answers1

1

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.

Jeroen
  • 60,696
  • 40
  • 206
  • 339