Silent refresh not working with OIDC-client in Angular 5

Anthony Giretti picture Anthony Giretti · Feb 14, 2018 · Viewed 13.4k times · Source

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?

Answer

Janne Harju picture Janne Harju · Jun 26, 2018

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>