Implementing a silent token renew in Angular for the OpenID Connect Implicit flow

This article shows how to implement a silent token renew in Angular using IdentityServer4 as the security token service server. The SPA Angular client implements the OpenID Connect Implicit Flow ‘id_token token’. When the id_token expires, the client requests new tokens from the server, so that the user does not need to authorise again.

Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow

History

2019-09-20: Updated ASP.NET Core 3.0, Angular 8.2.6
2018-06-22: Updated ASP.NET Core 2.1, Angular 6.0.6, ASP.NET Core Identity 2.1

Full history:
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow#history

Other posts in this series:

When a user of the client app authorises for the first time, after a successful login on the STS server, the AuthorizedCallback function is called in the Angular application. If the server response and the tokens are successfully validated, as defined in the OpenID Connect specification, the silent renew is initialized, and the token validation method is called.

public AuthorizedCallback() {
        
	...
		
	if (authResponseIsValid) {
		
		...

		if (this._configuration.silent_renew) {
			this._oidcSecuritySilentRenew.initRenew();
		}

		this.runTokenValidatation();

		this._router.navigate([this._configuration.startupRoute]);
	} else {
		this.ResetAuthorizationData();
		this._router.navigate(['/Unauthorized']);
	}

}

The OidcSecuritySilentRenew Typescript class implements the iframe which is used for the silent token renew. This iframe is added to the parent HTML page. The renew is implemented in an iframe, because we do not want the Angular application to refresh, otherwise for example we would lose form data.

...

@Injectable()
export class OidcSecuritySilentRenew {

    private expiresIn: number;
    private authorizationTime: number;
    private renewInSeconds = 30;

    private _sessionIframe: any;

    constructor(private _configuration: AuthConfiguration) {
    }

    public initRenew() {
        this._sessionIframe = window.document.createElement('iframe');
        console.log(this._sessionIframe);
        this._sessionIframe.style.display = 'none';

        window.document.body.appendChild(this._sessionIframe);
    }

    ...
}

The runTokenValidatation function starts an Observable timer. The application subscribes to the Observable which executes every 3 seconds. The id_token is validated, or more precise, checks that the id_token has not expired. If the token has expired and the silent_renew configuration has been activated, the RefreshSession function will be called, to get new tokens.

private runTokenValidatation() {
	let source = Observable.timer(3000, 3000)
		.timeInterval()
		.pluck('interval')
		.take(10000);

	let subscription = source.subscribe(() => {
		if (this._isAuthorized) {
			if (this.oidcSecurityValidation.IsTokenExpired(this.retrieve('authorizationDataIdToken'))) {
				console.log('IsAuthorized: isTokenExpired');

				if (this._configuration.silent_renew) {
					this.RefreshSession();
				} else {
					this.ResetAuthorizationData();
				}
			}
		}
	},
	function (err: any) {
		console.log('Error: ' + err);
	},
	function () {
		console.log('Completed');
	});
}

The RefreshSession function creates the required nonce and state which is used for the OpenID Implicit Flow validation and starts an authentication and authorization of the client application and the user.

public RefreshSession() {
        console.log('BEGIN refresh session Authorize');

        let nonce = 'N' + Math.random() + '' + Date.now();
        let state = Date.now() + '' + Math.random();

        this.store('authStateControl', state);
        this.store('authNonce', nonce);
        console.log('RefreshSession created. adding myautostate: ' + this.retrieve('authStateControl'));

        let url = this.createAuthorizeUrl(nonce, state);

        this._oidcSecuritySilentRenew.startRenew(url);
    }

The startRenew sets the iframe src to the url for the OpenID Connect flow. If successful, the id_token and the access_token are returned and the application runs without any interupt.

public startRenew(url: string) {
        this._sessionIframe.src = url;

        return new Promise((resolve) => {
            this._sessionIframe.onload = () => {
                resolve();
            }
        });
}

IdentityServer4 Implicit Flow configuration

The STS server, using IdentityServer4 implements the server side of the OpenID Implicit flow. The AccessTokenLifetime and the IdentityTokenLifetime properties are set to 30s and 10s. After 10s the id_token will expire and the client application will request new tokens. The access_token is valid for 30s, so that any client API requests will not fail. If you set these values to the same value, then the client will have to request new tokens before the id_token expires.

new Client
{
	ClientName = "angularclient",
	ClientId = "angularclient",
	AccessTokenType = AccessTokenType.Reference,
	AccessTokenLifetime = 30,
	IdentityTokenLifetime = 10,
	AllowedGrantTypes = GrantTypes.Implicit,
	AllowAccessTokensViaBrowser = true,
	RedirectUris = new List<string>
	{
		"https://localhost:44311"

	},
	PostLogoutRedirectUris = new List<string>
	{
		"https://localhost:44311/Unauthorized"
	},
	AllowedCorsOrigins = new List<string>
	{
		"https://localhost:44311",
		"http://localhost:44311"
	},
	AllowedScopes = new List<string>
	{
		"openid",
		"dataEventRecords",
		"dataeventrecordsscope",
		"securedFiles",
		"securedfilesscope",
		"role"
	}
}

When the application is run, the user can login, and the tokens are refreshed every ten seconds as configured on the server.

Links:

http://openid.net/specs/openid-connect-implicit-1_0.html

https://github.com/IdentityServer/IdentityServer4

https://identityserver4.readthedocs.io/en/release/

10 comments

  1. […] Implementing a silent token renew in Angular for the OpenID Connect Implicit flow (Damien Bowden) […]

  2. […] Implementing a silent token renew in Angular for the OpenID Connect Implicit flow by Damien Bowden. […]

  3. Andy Smith · · Reply

    Hi, thanks for this. This must have been a lot of work. I tried getting IdentityServerWithIdentitySQLite, ResourceServer, and AngularClient all working. Everything works fine until, after successfully authenticating, AngularClient tries to call ResourceServer for the ‘dataevenrecords’ page. I get this error:

    “No ‘Access-Control-Allow-Origin’ header is present on the requested resource”

    Googling leads me to believe it’s a CORS config issue, but I notice that AngularClient has this in its Startup.cs:

    services.AddCors(options => {
    options.AddPolicy(“AllowAllOrigins”,
    builder => {
    builder
    .AllowAnyOrigin()
    .AllowAnyHeader()
    .AllowAnyMethod();
    });
    });

    … and the ResourceServer has this in its Startup.cs:

    var policy = new Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy();
    policy.Headers.Add(“*”);
    policy.Methods.Add(“*”);
    policy.Origins.Add(“*”);
    policy.SupportsCredentials = true;

    services.AddCors(x => x.AddPolicy(“corsGlobalPolicy”, policy));

    So it seems like there shouldn’t be any CORS issues. Is this caused by something else? Any help is much appreciated.

    Thanks,
    Andy

  4. Bryian Tan · · Reply

    The error is a little misleading. Make sure the AccessTokenType is AccessTokenType.Jwt instead of AccessTokenType.Reference. Restart the app and the error should go away

  5. Hi – I’m a little – no, a lot – confused about something. A point in the right direction would be really helpful, as I think I’m missing something (I’m new to this OIDC stuff).

    I’m using the implicit flow with Angular 5, and my project is based on the “IdentityServerAngularImplicitFlow” project, specifically using the “AngularClient” project approach.
    I’m using the silent refresh functionality described in the above article, but I don’t understand the purpose of renewing the tokens whenever the id token expires? In the example code the ID token expires every 20 seconds, but the bearer token expires in 3600 seconds. To me (and I acknowledge that I am certain the problem is with me, not with the approach!) it seems like an awful extra communication getting a new set of tokens every twenty seconds when the original token would last for an hour. I can understand that we wouldn’t want to wait an hour, but… well, I think I’m just missing something – probably an important concept.

    Thank you in advance.

    1. The client uses the id_token expired time to refresh both tokens. If you want your access token to be vaild for an hour, you should set the id_token to maybe 3550, so that the application will refresh before the access token expires. The access token is used for the API access. As long as this token is valid, you will receive data

      Greetings Damien

  6. […] here I follow this article where I create an iframe and add it to the body. I create my own url (similar to the one made by […]

  7. thank you sir… this is a very big help….

    1. Hi Ervs

      Thanks

  8. […] 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 […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: