The article shows how the OpenID Connect Session Management can be implemented in an Angular application. The OpenID Connect Session Management 1.0 provides a way of monitoring the user session on the server using iframes. IdentityServer4 implements the server side of the specification. This does not monitor the lifecycle of the tokens used in the browser application. This session only monitors the server session. This has nothing to do with the OpenID tokens used by the SPA application.
Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow
Code: Angular auth module
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:
- Authorization Policies and Data Protection with IdentityServer4 in ASP.NET Core
- Angular OpenID Connect Implicit Flow with IdentityServer4
- Angular secure file download without using an access token in URL or cookies
- Full Server logout with IdentityServer4 and OpenID Connect Implicit Flow
- IdentityServer4, Web API and Angular in a single project
- Extending Identity in IdentityServer4 to manage users in ASP.NET Core
- Implementing a silent token renew in Angular for the OpenID Connect Implicit flow
- OpenID Connect Session Management using an Angular application and IdentityServer4
The OidcSecurityCheckSession class implements the Session Management from the specification. The init function creates an iframe and adds it to the window document in the DOM. The iframe uses the ‘authWellKnownEndpoints.check_session_iframe’ value, which is the connect/checksession API got from the ‘.well-known/openid-configuration’ service.
The init function also adds the event for the message, which is specified in the OpenID Connect Session Management documentation.
init() { this.sessionIframe = window.document.createElement('iframe'); this.oidcSecurityCommon.logDebug(this.sessionIframe); this.sessionIframe.style.display = 'none'; this.sessionIframe.src = this.authWellKnownEndpoints.check_session_iframe; window.document.body.appendChild(this.sessionIframe); this.iframeMessageEvent = this.messageHandler.bind(this); window.addEventListener('message', this.iframeMessageEvent, false); return Observable.create((observer: Observer<any>) => { this.sessionIframe.onload = () => { observer.next(this); observer.complete(); } }); }
The pollServerSession function, posts a message every 3 seconds to the iframe which checks if the session on the server has been changed. The session_state is the value returned in the HTTP callback from a successful authorization.
pollServerSession(session_state: any, clientId: any) { let source = Observable.timer(3000, 3000) .timeInterval() .pluck('interval') .take(10000); let subscription = source.subscribe(() => { this.oidcSecurityCommon.logDebug(this.sessionIframe); this.sessionIframe.contentWindow.postMessage(clientId + ' ' + session_state, this.authConfiguration.stsServer); }, (err: any) => { this.oidcSecurityCommon.logError('pollServerSession error: ' + err); }, () => { this.oidcSecurityCommon.logDebug('checksession pollServerSession completed'); }); }
The messageHandler handles the callback from the iframe. If the server session has changed, the output onCheckSessionChanged event is triggered.
private messageHandler(e: any) { if (e.origin === this.authConfiguration.stsServer && e.source === this.sessionIframe.contentWindow ) { if (e.data === 'error') { this.oidcSecurityCommon.logWarning('error from checksession messageHandler'); } else if (e.data === 'changed') { this.onCheckSessionChanged.emit(); } else { this.oidcSecurityCommon.logDebug(e.data + ' from checksession messageHandler'); } } }
The onCheckSessionChanged is a public EventEmitter output for this provider.
@Output() onCheckSessionChanged: EventEmitter<any> = new EventEmitter<any>(true);
The OidcSecurityService provider subscribes to the onCheckSessionChanged event and uses its onCheckSessionChanged function to handle this event.
this.oidcSecurityCheckSession.onCheckSessionChanged.subscribe(() => { this.onCheckSessionChanged(); });
After a successful login, and if the tokens are valid, the client application checks if the checksession should be used, and calls the init method and subscribes to it. When ready, it uses the pollServerSession function to activate the monitoring.
if (this.authConfiguration.start_checksession) { this.oidcSecurityCheckSession.init().subscribe(() => { this.oidcSecurityCheckSession.pollServerSession( result.session_state, this.authConfiguration.client_id ); }); }
The onCheckSessionChanged function sets a public boolean which can be used to implement the required application logic when the server sesion has changed.
private onCheckSessionChanged() { this.oidcSecurityCommon.logDebug('onCheckSessionChanged'); this.checkSessionChanged = true; }
In this demo, the navigation bar allows to Angular application to refresh the session if the server session has changed.
<li> <a class="navigationLinkButton" *ngIf="securityService.checkSessionChanged" (click)="refreshSession()">Refresh Session</a> </li>
When the application is started, the unchanged message is returned.
Then open the server application in a tab in the same browser session, and logout.
And the client application notices tht the server session has changed and can react as required.
Links:
[…] OpenID Connect Session Management an Angular application using IdentityServer4 (Damien Bowden) […]
[…] OpenID Connect Session Management an Angular application using IdentityServer4 […]
Source code for the implementation
see the link art the start
Should this work across browsers? If a user signs in to a client in chrome and then also in edge, but then signs out from edge which causes a sign out at the OP, should the chrome client detect a checksession changed event since that user is now signed out at the OP?