8

I have an issue with the silent refresh with oidc-client. The signin works fine and I'm able to acquire a token. However, the silent refresh doesn't fire, nothing happens. When I subscribe to methods that check token expiry (methods in subscribeevents in authservice.ts below), these methods never fire - and the method isLoggedIn() always return true even if the token has expired.

Here is my code :

import { Component, OnInit } from '@angular/core';
import { UserManager } from 'oidc-client';
import { getClientSettings } from '../openIdConnectConfig';
import { AuthService } from '../services/auth.service';
    
@Component({
    selector: 'app-silentrefresh',
    templateUrl: './silentrefresh.component.html',
    styleUrls: ['./silentrefresh.component.css']
})
export class SilentRefreshComponent implements OnInit {
    constructor(private _authService:AuthService) {
    }
    
    ngOnInit() {
        this._authService.refreshCallBack();
    }
}

Then my authservice :

import { UserManagerSettings, UserManager, User } from 'oidc-client';
import { Injectable } from '@angular/core';
import { getClientSettings } from '../openIdConnectConfig';
    
@Injectable()
export class AuthService {

    private _manager = new UserManager(getClientSettings());
    private _user: User = null;

    constructor() {
        this._manager.getUser().then(user => {
            this._user = user;
        });

        this._manager.events.addUserLoaded(user => {
            this._user = user;
        });

        this.subscribeevents();
    }

    public isLoggedIn(): boolean {
        return this._user != null && !this._user.expired;
    }

    public getClaims(): any {
        return this._user.profile;
    }

    public subscribeevents(): void {
        this._manager.events.addSilentRenewError(() => {
            console.log("error SilentRenew");
        });

        this._manager.events.addAccessTokenExpiring(() => {
            console.log("access token expiring");
        });

        this._manager.events.addAccessTokenExpired(() => {
            console.log("access token expired");
        });
    }

    public refreshCallBack(): void {
        console.log("start refresh callback");
        this._manager.signinSilentCallback()
            .then(data => { console.log("suucess callback") })
            .catch(err => {
                console.log("err callback");
            });
        console.log("end refresh callback");
    }

    getUser(): any {
        return this._user;
    }

    getName(): any {
        return this._user.profile.name;
    }

    getAuthorizationHeaderValue(): string {
        return `${this._user.token_type} ${this._user.access_token}`;
    }

    startAuthentication(): Promise<void> {
        return this._manager.signinRedirect();
    }

    completeAuthentication(): Promise<void> {
        return this._manager.signinRedirectCallback().then(user => {
            this._user = user;
        });
    }
}

And my config:

import { UserManagerSettings } from "oidc-client";

export function getClientSettings(): UserManagerSettings {
    return {
        authority: 'https://login.microsoftonline.com/136544d9-038e-4646-afff-10accb370679',
        client_id: '257b6c36-1168-4aac-be93-6f2cd81cec43',
        redirect_uri: 'http://localhost:4200/auth-callback',
        //redirect_uri: 'https://demoazureadconnectangular5.azurewebsites.net/auth-callback',
        post_logout_redirect_uri: 'http://localhost:4200/',
        //post_logout_redirect_uri: 'https://demoazureadconnectangular5.azurewebsites.net/',
        response_type: "id_token",
        scope: "openid profile",
        filterProtocolClaims: true,
        loadUserInfo: true,
        automaticSilentRenew: true,
        silent_redirect_uri: 'http://localhost:4200/assets/silentrefresh',
        metadata: {
            issuer: "https://sts.windows.net/136544d9-038e-4646-afff-10accb370679/",
            authorization_endpoint: "https://login.microsoftonline.com/136544d9-038e-4646-afff-10accb370679/oauth2/authorize",
            token_endpoint: "https://login.microsoftonline.com/136544d9-038e-4646-afff-10accb370679/oauth2/token",
            //jwks_uri: "https://login.microsoftonline.com/common/discovery/keys",
            jwks_uri: "http://localhost:4200/assets/keys.json",
            //jwks_uri: "https://demoazureadconnectangular5.azurewebsites.net/assets/keys.json",
            //jwks_uri: "http://localhost:50586/api/keys",
            signingKeys: [{ "ApiAccessKey": "NgixniZ0S1JHxo7GPEZYa38OBTxSA98AqJKDX5XqsJ8=" }]
        }
    };
}

I also tried to use a static page like this:

<head>
    <title></title>
</head>

<body>
    <script src="oidc-client.min.js"></script>
    <script>
        var usermanager = UserManager().signinSilentCallback()
            .catch((err) => {
                console.log(err);
            });
    </script>
</body>

It's never fired neither

In order to test, I've changed the ID token expiry to 10 min. I use Azure AD Connect (Open Id Connect in Azure) and Microsoft says it's not fully compatible with Open ID Connect standard... So I don't know if it's on my side or Azure side.

Somebody can help me to solve this?

Johan Aspeling
  • 620
  • 1
  • 9
  • 32
Anthony Giretti
  • 277
  • 1
  • 2
  • 7

5 Answers5

3

The problem is that you are not asking access_token from azure AD, only id_token. You must set response_type to id_token token to get both tokens. This change will need also few more parameters. For example resource for your backend. I have answered similar question here. I'm using also Angular 5 and oidc client. https://stackoverflow.com/a/50922730/8081009 And I answer you here also before https://github.com/IdentityModel/oidc-client-js/issues/504#issuecomment-400056662 Here is what you need to set to get silent renew working.

includeIdTokenInSilentRenew: true
extraQueryParams: {
      resource: '10282f28-36ed-4257-a853-1bf404996b18'
}
response_type: 'id_token token',
scope: 'openid'
loadUserInfo: false,
automaticSilentRenew: true,
silent_redirect_uri: `${window.location.origin}/silent-refresh.html`,
metadataUrl: 'https://login.microsoftonline.com/YOUR_TENANT_NAME.onmicrosoft.com/.well-known/openid-configuration',
signingKeys: [
    add here keys from link below
]

https://login.microsoftonline.com/common/discovery/keys

I'm also using different static page for callback endpoint with silent renew because this way user won't notice a thing. This page is minimum possible so oidc won't load whole angular application to hidden iframe what it is using for silent renew. So this is recommended to be more efficient.

<head>
  <title></title>
</head>

<body>
  <script src="assets/oidc-client.min.js"></script>
  <script>
    new Oidc.UserManager().signinSilentCallback()
      .catch((err) => {
        console.log(err);
      });
  </script>
</body>
Janne Harju
  • 744
  • 1
  • 8
  • 23
  • I totally agree. But in February access_token was not supported but Open Is Connect on azure AD. Asking access_token was throwing an error. Now it’s supported in implicit flow. That’s it. – Anthony Giretti Jul 02 '18 at 03:35
2
  1. Check if you have correct redirect URI in the database.

  2. Check you have added the following in your angular.json file:

    ...
    "assets": [
        "src/assets",
        "silent-refresh.html",
        "oidc-client.min.js"
        .....
    ],
    ...
    
  3. Check silent-refresh.html:

    <script src="oidc-client.min.js"></script><script>
        var mgr = new Oidc.UserManager();
        mgr.signinSilentCallback().catch(error => {
            console.error(error);
        });
    </script>
    
  4. Check you do not create more than one instance of UserManager

  5. You can do either way - automaticSilentRenew: false, or automaticSilentRenew: true, I will recommend using automaticSilentRenew: false and trigger an event on expiring.

    https://github.com/IdentityModel/oidc-client-js/wiki

    public renewToken() {
        return this.manager.signinSilent().then(u => {
            this.user = u;
        }).catch(er => {
            console.log(er);
        });
    }
    
    this.manager.events.addAccessTokenExpiring(x => {
        console.log('Acess token expiring event');
        this.renewToken().then(u => {
            console.log('Acess token expiring event renew success');
        });
    });
    

If the above things do not work then check the identity server code.


Identity server code

Startup

services.AddIdentityServer(options =>
    {
        options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
        options.Authentication.CookieSlidingExpiration = true;
    });
services.AddAuthentication(x => x.DefaultAuthenticateScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme);

Logout

await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme);

Thanks to https://github.com/IdentityModel/oidc-client-js/issues/911#issuecomment-617724445

Johan Aspeling
  • 620
  • 1
  • 9
  • 32
Ishika Jain
  • 147
  • 1
  • 9
1

Simplest reason can be, not adding silent renew url as a redirect url in identity server configuration.

In your identity server database, redirect urls for your clients should be like this

redirectUrls: [http://localhost:4200/assets/silentrefresh, http://localhost:4200/auth-callback]
Lasal Sethiya
  • 201
  • 4
  • 17
0

I have used some different approach to initate the silentRenw, instead of using

 automaticSilentRenew: true,

I decided to explicitly call the signInSilent();. Reason for doing the i was facing some issues as automaticSilentRenew: true, was not working as expected.

I initialized the event and method in my UserAuth class constructor that implements my interface

  constructor(private oidcSettings: CoreApi.Interfaces.Authentication.OpenIDConnectionSettings)
    {
     this.userManager.events.addAccessTokenExpiring(() =>
             {
                this.userManager.signinSilent({scope: oidcSettings.scope, response_type: oidcSettings.response_type})
                        .then((user: Oidc.User) =>
                        {
                            this.handleUser(user);
                        })
                        .catch((error: Error) =>
                        {
                           //Work around to handle to Iframe window timeout errors on browsers
                            this.userManager.getUser()
                                .then((user: Oidc.User) =>
                                {
                                    this.handleUser(user);
                                });
                        });
                });
    }

Where as handleUser is just check for logged in user.

So if you initialize the signInSilent process in your constructor and then call signInSilent complete i.e. callback it may work.

Sohan
  • 4,146
  • 2
  • 30
  • 50
  • 1
    Thank you, but addaccessTokenExpiring never fire.... the reason is : I get an IdToken and not an AccessToken from Azure Ad – Anthony Giretti Feb 15 '18 at 12:24
  • Ohh i see, in that case signIn silent will work only with access_token. I hope the openID connect flow is either implicit or authorization flow? – Sohan Feb 15 '18 at 13:45
  • Implicit (spa) or basic (backend only). I can’t renew ID token with OIDC-Client because Azure AD , this last one doesn’t provide access_token. With other providers I don’t know because I have never tried – Anthony Giretti Feb 15 '18 at 19:11
  • Other IDP providers like GLUU or OpenAM does support proper openID flow and returns the access_token and id_token. – Sohan Feb 16 '18 at 07:25
  • I have no choice to use azure AD – Anthony Giretti Feb 16 '18 at 13:08
0
Not sure what oidc-client.js version you are using, this should never have worked.

```
new Oidc.UserManager().signinSilentCallback()
                  .catch((err) => {
                      console.log(err);
                  });
``

Usermanager is in **Oidc** object. 
hashbytes
  • 639
  • 1
  • 5
  • 21