Angular-auth-oidc-client: Silent Renew - Renew stops checking token timestamps

Created on 26 Oct 2020  路  28Comments  路  Source: damienbod/angular-auth-oidc-client

Env

  • We are using version 11.2.1
  • Angular 10.2
  • rxjs 6.6.3
  • Typescript 4.0.3
  • Implicit flow
  • There are no errors in the browser Console
  • There are no errors in browser Network
  • There are no errors from the STS (IDS4)

Issue

1) Silent renew runs once and the tokens are renewed
2) No errors on the STS console
3) Subsequent checks skip the check for expiry

First time silent renew compares the timestamp:-

Checking: silentRenewRunning: false id_token: true userData: true
Has id_token expired: false, 1603536078000 > 1603535806261
Has access_token expired: false, 1603535811129 > 1603535806262
silent renew finished!

Timestamps show token expired - Renew works fine and there are no errors on the STS console (IDS4)

Checking: silentRenewRunning: false id_token: true userData: true
Has id_token expired: false, 1603536078000 > 1603535812263
Has access_token expired: true, 1603535811129 > 1603535812263
starting silent renew...
BEGIN refresh session Authorize Iframe renew
RefreshSession created. adding myautostate:  Array [ "f9b21f51375da50cfbdffbfc8a808c6b10MFtZrqx" ]
....
STS server: http://an.ip.address
currentUrl to check auth with:
....
BEGIN authorizedCallback, no auth data
history clean up inactive
Object { id_token: 
....
removed event listener from IFrame
authorizedCallback created, begin token validation
Getting signinkeys from Array [ "http://an.ip.address/.well-known/openid-configuration/jwks" ]
silent renew finished!

Then subsequent silent renews don't check the timestamps

Checking: silentRenewRunning: false id_token: true userData: true
silent renew finished!

Digging deeper

The key value for myClientName_storageSilentRenewRunning is not being reset after the tokens are renewed. But manually setting this key value to something other than running makes the silentRenew check the timestamps again.

IE this code doesn't return null anymore and the timestamp evaluation runs:-

            const shouldBeExecuted = userDataFromStore && !isSilentRenewRunning && idToken;
            if (!shouldBeExecuted) {
                return of(null);
            }

After the refresh In the code (around 2900)

            .subscribe(() => {
            this.loggerService.logDebug('silent renew finished!');
            if (this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()) {
                this.flowsDataService.resetSilentRenewRunning();
            }

resetSilentRenewRunning is not being called so the key value isn't updated from "running"

In the config the default value for useRefreshToken is false. Is this why below code returns false?

this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()

Some help on this please would be great
Thanks in advance,
Nigel.

investigate

Most helpful comment

Hi all,

Thanks to the valuable feedback from other ticket subscribers about the silentrenew.html not being copied.

Unfortunately that is not the case for us. Silent renew is copied and always present on the server

Key issue for us is :-

The key value for myClientName_storageSilentRenewRunning is not being reset after the tokens are renewed. But manually setting this key value to something other than running makes the silentRenew check the timestamps again

And this code (Around line 2949) always returns false:-

if (this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()) { }

Please re-open the ticket as based on initial submission data it has not been solved

Thanks
Nigel

All 28 comments

I am having the same issue. See the console logs below (the token is expired btw). My silent-renew iframe is also empty in the elements tab in chrome dev tools. I am also not seeing calls in the network tab to the silent-renew.html.

Checking: silentRenewRunning: true id_token: true userData: true angular-auth-oidc-client.js:180 silent renew finished! angular-auth-oidc-client.js:180 Checking: silentRenewRunning: true id_token: true userData: true angular-auth-oidc-client.js:180 silent renew finished! angular-auth-oidc-client.js:180 Checking: silentRenewRunning: true id_token: true userData: true angular-auth-oidc-client.js:180 silent renew finished!

@mluker @OCATCloudApps I'll look into this. What is happening is that the renew process is failing in your system and your browser. The reason is unclear, could be a bug, misconfiguration or the browser.

More and more browsers are blocking the silent renew using iframes and it looks like the renew using refresh tokens will have to be used in the future.

Greetings Damien

@damienbod for more context here is my setup.

  • I am using Azure B2c
  • Login/Auth works as expected
  • Tokens expire and do not refresh. When this happens I am still Authorized
  • iFrame traffic never shows up in network tab in chrome

console when tokens are expired

Checking: silentRenewRunning: false id_token: true userData: true
silent renew finished!
Checking: silentRenewRunning: false id_token: true userData: true
silent renew finished!
Checking: silentRenewRunning: false id_token: true userData: true
silent renew finished!

In app.module

export function loadConfig(oidcConfigService: OidcConfigService) {
  return () =>
    oidcConfigService.withConfig({
      stsServer: 'https://foobar.b2clogin.com/foobar.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_SignIn-Theming-Dev',
      authWellknownEndpoint: 'https://foobar.b2clogin.com/foobar.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_SignIn-Theming-Dev',
      redirectUrl: 'http://localhost:4200/Home'
      postLogoutRedirectUri: window.location.origin,
      clientId: 'foobarId',
      scope: 'openid',
      responseType: 'id_token token',
      silentRenew: true,
      autoUserinfo: false,
      silentRenewUrl: window.location.origin + '/silent-renew.html',
      logLevel: LogLevel.Error,
      unauthorizedRoute: window.location.origin + '/Home',
      forbiddenRoute: window.location.origin + '/Home',
      renewTimeBeforeTokenExpiresInSeconds: 60
    });

In app.component

this.oidcSecurityService
      .checkAuth().subscribe((isAuthenticated) => {
        .........
      });

this.loginCompleted = this.oidcSecurityService.stsCallback$.subscribe(
      () => {
          ......
      });

In angular.json

"assets": [
              "src/assets",
              "src/favicon.ico",
              "src/web.config",
              "src/silent-renew.html"
            ],

In your code, when it stops working for me the line 'const shouldBeExecuted = userDataFromStore && !isSilentRenewRunning && idToken;' always comes back false.

 startTokenValidationPeriodically(repeatAfterSeconds) {
        if (!!this.intervallService.runTokenValidationRunning || !this.configurationProvider.openIDConfiguration.silentRenew) {
            return;
        }
        this.loggerService.logDebug(`starting token validation check every ${repeatAfterSeconds}s`);
        const periodicallyCheck$ = this.intervallService.startPeriodicTokenCheck(repeatAfterSeconds).pipe(switchMap(() => {
            const idToken = this.authStateService.getIdToken();
            const isSilentRenewRunning = this.flowsDataService.isSilentRenewRunning();
            const userDataFromStore = this.userService.getUserDataFromStore();
            this.loggerService.logDebug(`Checking: silentRenewRunning: ${isSilentRenewRunning} id_token: ${!!idToken} userData: ${!!userDataFromStore}`);
            const shouldBeExecuted = userDataFromStore && !isSilentRenewRunning && idToken;
            if (!shouldBeExecuted) {
                return of(null);
            }
            const idTokenHasExpired = this.authStateService.hasIdTokenExpired();
            const accessTokenHasExpired = this.authStateService.hasAccessTokenExpiredIfExpiryExists();
            if (!idTokenHasExpired && !accessTokenHasExpired) {
                return of(null);
            }
            if (!this.configurationProvider.openIDConfiguration.silentRenew) {
                this.flowsService.resetAuthorizationData();
                return of(null);
            }
            this.loggerService.logDebug('starting silent renew...');
            this.flowsDataService.setSilentRenewRunning();
            if (this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()) {
                // Refresh Session using Refresh tokens
                return this.refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens();
            }
            return this.refreshSessionIframeService.refreshSessionWithIframe();
        }));
        this.intervallService.runTokenValidationRunning = periodicallyCheck$
            .pipe(catchError(() => {
            this.flowsDataService.resetSilentRenewRunning();
            return throwError('periodically check failed');
        }))
            .subscribe(() => {
            this.loggerService.logDebug('silent renew finished!');
            if (this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()) {
                this.flowsDataService.resetSilentRenewRunning();
            }
        }, (err) => {
            this.loggerService.logError('silent renew failed!', err);
        });
    }

::UPDATE::
@damienbod you were correct, I had it configured incorrectly. I am using implicit flow and I had the silent-renew script copied over from the PKCE sample.

Hi,

If it helps I debugged it and discovered that this code (Around line 2949) always returns false:-

if (this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()) { }

Which means that this code (around line 2950) is never executed:-

this.flowsDataService.resetSilentRenewRunning();

I gaffer taped the code by adding an else:-

            .subscribe(() => {
            this.loggerService.logDebug('silent renew finished!');
            if (this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()) {
                this.flowsDataService.resetSilentRenewRunning();
            }

            // Added by NH
            else {
                this.flowsDataService.resetSilentRenewRunning(); // regardless of isCurrentFlowCodeFlowWithRefeshTokens test
            }
           // End added by NH

I know very little about the code and the security aspects so please don't accept above as any sort of fix. It's just a temporary workaround so the silent renew works for us and we can continue developing while Damien looks at it

Kind reagrds
Nigel.

Hi,
I was wondering if there has been any progress on this?
Thanks,
Nigel

Hi Nigel

WIll have a look at this when I get time. Going through some issues.

Greetings Damien

This is also happening for me when using the client with Azure B2C. The access token refreshes correctly for the first time and then the [client_id]_storageSilentRenewRunning value gets stuck at "running" in Session Storage preventing the token from being renewed again.

I am having to manually set the [client_id]_storageSilentRenewRunning to null if it has been stuck at "running" for more than 60 seconds, to continually refresh the tokens.

Hello everyone! I spent a lot of time finding solution of same problem. SilentRenew runs for the first time, after which the parameter [client-id]_storageSilentRenewRunning was set to "running" and was no longer reset. Further, the silent renew process just stops working, adding other additional problems like #801 or #790

In the end, it turned out that in my case the silent-renew.html file was not copied to the dist folder. As a result, the current page is added to the iframe, instead of the required content from silent-renew.html. After restore copiing silent-renew.html to dist folder all works fine.

I hope it helps anybody.

@OCATCloudApps FYI.

Thanks to @Stefano95054, I managed to resolve this issue by adding the silent-renew.html file to the angular.json build assets (projects > [name-of-project] > architect > build > options > assets) and adding a 'postLoginRoute' to the OpenIdConfiguration config object.

Hi all,

Thanks to the valuable feedback from other ticket subscribers about the silentrenew.html not being copied.

Unfortunately that is not the case for us. Silent renew is copied and always present on the server

Key issue for us is :-

The key value for myClientName_storageSilentRenewRunning is not being reset after the tokens are renewed. But manually setting this key value to something other than running makes the silentRenew check the timestamps again

And this code (Around line 2949) always returns false:-

if (this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()) { }

Please re-open the ticket as based on initial submission data it has not been solved

Thanks
Nigel

Hi just for info:

responseType: 'id_token token' == implicit flow

responseType: 'code ' = =code with PKCE

refresh tokens are with code flow with PKCE

B2C is missing features for refresh, you should maybe stick with the iframes renew if you must use B2C

Greetings Damien

Is there any workaround for this problem? When will this be fixed? Should i use pkce flow instead?

Hi xaviergxf,

My 'gaffer tape' fix (see above) has been working fine for us in a production environment for some months now. But I'm not an O-Auth / OIDC security specialist so please use it with caution.

It would be good if Damien could give us an update on this please. Especially because it's a library that is recommended for use with IDS4

Cheers,
Nigel.

Can you give us an example repo where we can reproduce the issue?

Hi Fabian,

I'm afraid I can't make a repo of our code because it's production code for a client. Perhaps xaviergxf can provide you with this?

However, there isn't much difference between what we are using and the example given for using OIDC Connect, with IDS4 and an Angular App. My first post at the very top has a lot of info about our environment, versions etc. You should be able to quite quickly create a new Angular client and see the problem

FYI This wasn't an issue when using previous version of the client

Cheers,
Nigel

Hey, thanks for your answer.

I fully understand that the code of your client can not be made public. Therefore a small repo to reproduce the issue would be helpful. You can use my identity server which is used in the samples and create a Stackblitz or something that helps you to get started right away and us to see the issue. Help us to help you :-) Thanks.

In the config the default value for useRefreshToken is false. Is this why below code returns false?
this.flowHelper.isCurrentFlowCodeFlowWithRefeshTokens()

isCurrentFlowCodeFlowWithRefeshTokens returns TRUE if the current flow is a code flow (responsetype must include or be code) and the property useRefreshToken on the config is set to true. Did you check that?

Thanks

Hi,

Here's my config code:-

```export function configureAuth(oidcConfigService: OidcConfigService) {
return () =>
oidcConfigService.withConfig({

      stsServer: environment.stsServerUrl,
      redirectUrl: environment.clientUrl,
      postLogoutRedirectUri: environment.clientUrl,
      clientId: environment.client_id,
      scope: environment.scopes,
      responseType: 'id_token token',
      silentRenew: true,
      silentRenewUrl: `${environment.clientUrl}/silent_renew.html`,
      renewTimeBeforeTokenExpiresInSeconds : 10,
      logLevel: LogLevel.None,
      // useRefreshToken: true // Setting to true Causes ERROR TypeError: this.configurationProvider.openIDConfiguration is null

  });

}
```

In environment.ts scopes is (redacted the API names)

scopes: 'openid profile AN_API_1 AN_API_2 AN_API_3'

We had to comment out useRefreshToken: true because setting it gives this error:-

this.configurationProvider.openIDConfiguration is null

When calling

this.oidcSecurityService.authorize();

Does this help?
Cheers,
Nigel.

Service configured in providers like this:-

OidcConfigService, { provide: APP_INITIALIZER, useFactory: configureAuth, deps: [OidcConfigService], multi: true, },

Sorry, closed this accidentally.

Just to summarize:

1) When using the Implicit Flow and NO refresh tokens the first silent renew check the timestamps once and future calls do not respect the timestamps anymore, is that correct?

_In addition to that_ we have the issue, that

2) when you set useRefreshToken: true _with the config we see above_ (meaning you use refresh tokens AND silent renew) the error this.configurationProvider.openIDConfiguration is null occurs when calling authorize. Is that correct?

For 2) if that is the case we should catch the error better but AFAIK using refresh tokens AND silent renew does not make sense, but maybe I am wrong @damienbod ?

Thanks

Hi Fabian,

1) Yes this is the case - Future calls (2nd, 3rd etc) do not evaluate (respect) the timestamp

First call debug:-

Checking: silentRenewRunning: false id_token: true userData: true
Has id_token expired: false, 1603536078000 > 1603535806261
Has access_token expired: false, 1603535811129 > 1603535806262
silent renew finished!

Everything fine

Subsequent call debug:-

Checking: silentRenewRunning: false id_token: true userData: true
silent renew finished!

Note that the has id / access token expired code is not being executed in all subsequent silent renew calls
Also note that we haven't set a value (neither true or false) to the property useRefreshToken but silent renew still runs albeit partially after the first run

2) In earlier versions using a refresh token and silent renew was possible and, correctly if I'm wrong documented.

Perhaps if the error was caught we could properly set the useRefreshToken property and refreshing would function as normal?

Thanks for your help!

Hey, thanks for your response.

What do you want to use to refresh the tokens: Silent renew or Refresh Token mechanism? In Implicit Flow you can ONLY use silent renew, refresh tokens is only supported with code flow.

You have to decide and can not use both.

Thanks

Hi Fabian,

Sorry, my bad I'm mixing up two issues and two protocols! We would like to continue using silent renew for the moment then in the near future refresh token

Firstly silent renew:-

1) When using silent renew should useRefreshToken be set to false even though we're not using it?
2) Why does the first silent renew call check for token expiration and subsequent calls do not (as above)
3) Issue is debug reports that silent renew runs every 10 seconds and finishes but doesn't actually renew
4) The file silent_renew.html definitely exists (re another issue mentioned above)

And now Refresh tokens:-

1) The error when setting useRefreshToken is presumably caused be a misconfiguration
2) Any chance this error could be caught and more detail logged?
3) This is probably my fault - Can you point me to a client config example for refresh token please?
4) When using refresh tokens should silentRenew be set to false even though it's not being used?

Is the config read on the basis of the true / false state of useRefreshToken or useRefreshToken or perhaps the presence of the silentRenewUrl parameter? This would be good to know as often when testing it would be normal to leave some parameters set for either protocol. Eg. Leave the silentRenewUrl parameter even if it's not being used.

Thanks in advance,
Nigel.

Hey, thanks for the response. Now we are talking! I can clearly see what your plans are now and you broke your problems down into multiple questions. That is amazing and helps us a lot.

So, let us go through this:

We would like to continue using silent renew for the moment then in the near future refresh token

Nice, thanks. Good plan :-)

When using silent renew should useRefreshToken be set to false even though we're not using it?

No, you do not have to set it explicitly. It is set to false per default.

Why does the first silent renew call check for token expiration and subsequent calls do not (as above)
Issue is debug reports that silent renew runs every 10 seconds and finishes but doesn't actually renew
The file silent_renew.html definitely exists (re another issue mentioned above)

That could be a bug, We are using this config (posted from you earlier) to check this:

  return () =>
      oidcConfigService.withConfig({
          stsServer: environment.stsServerUrl,
          redirectUrl: environment.clientUrl,
          postLogoutRedirectUri: environment.clientUrl,
          clientId: environment.client_id,
          scope: environment.scopes,
          responseType: 'id_token token',
          silentRenew: true,
          silentRenewUrl: `${environment.clientUrl}/silent_renew.html`,
          renewTimeBeforeTokenExpiresInSeconds : 10,
          logLevel: LogLevel.None,
      });
}

This is implicit flow.

This is probably my fault - Can you point me to a client config example for refresh token please?

Yes, we have that in our samples: Find it Sample Code Flow Refresh Tokens.

But to use this you _have to use Code Flow_, currently your config is Implicit Flow.

When using refresh tokens should silentRenew be set to false even though it's not being used?

You can check the config in the sample repo.

Will use your config to check what is happening. Thanks for the clarifications.

Hi Fabian,
Thanks for the update
FYI the example refresh token config has silentRenew = true
That might be a bit confusing to newbies like me ;-)
Cheers,
Nigel.

Hey, thanks for the answer.

Ah, the silentRenew is a general switch if it is enabled or not. Maybe we should think of a better naming @damienbod ?

Thanks for the headsup!

Hi Fabian,
If you can please consider better handling of the error:-

this.configurationProvider.openIDConfiguration is null

When calling

oidcSecurityService.authorize();

This would help us debug why we are getting the error when trying to use refresh tokens in our Angular App

Thanks for all your help and have a great weekend!

Can you please try the config

 oidcConfigService.withConfig({
      stsServer: environment.stsServerUrl,
      redirectUrl: environment.clientUrl,
      postLogoutRedirectUri: environment.clientUrl,
      clientId: environment.client_id,
      scope: environment.scopes,
      responseType: 'id_token token',
      silentRenewUrl: `${window.location.origin}/silent-renew.html`,
      startCheckSession: true,
      silentRenew: true,
      logLevel: LogLevel.Debug,
    });

and tell me if this fits your error for Implicit Flow / silent renew (NOT refresh tokens?)

If you can please consider better handling of the error:-
this.configurationProvider.openIDConfiguration is null
When calling
oidcSecurityService.authorize();

If your config is null maybe you did not set it up correctly when starting the app?

A sample repository from you with the config from our example would really help...

Thanks.

Hi Fabian,
Apologies for not getting back to you sooner. We are in the middle of a major update to our platform and I haven't had a chance to try the config yet in test. Will set up a test Angular client that uses silent renew which I will be able to share with you ASAP
Kind regards,
Nigel.

Was this page helpful?
0 / 5 - 0 ratings