OpenID Connect Implicit Flow
This library is certified by OpenID Foundation. (RP Implicit and Config RP)
- Angular 4 onwards
- Supports OpenID Implicit Flow http://openid.net/specs/openid-connect-implicit-1_0.html
- Complete client side validation for REQUIRED features
- OpenID Connect Session Management 1.0 http://openid.net/specs/openid-connect-session-1_0.html
- AOT build
- Can be lazy loaded
Documentation : Quickstart | API Documentation | Changelog
Navigate to the level of your package.json and type
npm install angular-auth-oidc-client --saveor with yarn
yarn add angular-auth-oidc-clientor you can add the npm package to your package.json
"angular-auth-oidc-client": "4.0.1"and type
npm installImport the module and services in your module.
The OidcSecurityService has a dependency on the HttpClientModule which needs to be imported. The angular-auth-oidc-client module supports all versions of Angular 4.3 onwards.
import { NgModule, APP_INITIALIZER } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { AuthModule, OidcSecurityService, OpenIDImplicitFlowConfiguration, OidcConfigService, AuthWellKnownEndpoints } from 'angular-auth-oidc-client'; export function loadConfig(oidcConfigService: OidcConfigService) { console.log('APP_INITIALIZER STARTING'); return () => oidcConfigService.load_using_stsServer('https://localhost:44318'); } @NgModule({ imports: [ ... HttpClientModule, AuthModule.forRoot() ], declarations: [ ... ], providers: [ OidcConfigService, { provide: APP_INITIALIZER, useFactory: loadConfig, deps: [OidcConfigService], multi: true }, ... ], bootstrap: [AppComponent], })Set the AuthConfiguration properties to match the server configuration. At present only the 'id_token token' or the 'id_token' flows are supported.
export class AppModule { constructor( private oidcSecurityService: OidcSecurityService, private oidcConfigService: OidcConfigService, ) { this.oidcConfigService.onConfigurationLoaded.subscribe(() => { const openIDImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration(); openIDImplicitFlowConfiguration.stsServer = this.oidcConfigService.clientConfiguration.stsServer; openIDImplicitFlowConfiguration.redirect_url = this.oidcConfigService.clientConfiguration.redirect_url; // The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer // identified by the iss (issuer) Claim as an audience. // The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, // or if it contains additional audiences not trusted by the Client. openIDImplicitFlowConfiguration.client_id = this.oidcConfigService.clientConfiguration.client_id; openIDImplicitFlowConfiguration.response_type = this.oidcConfigService.clientConfiguration.response_type; openIDImplicitFlowConfiguration.scope = this.oidcConfigService.clientConfiguration.scope; openIDImplicitFlowConfiguration.post_logout_redirect_uri = this.oidcConfigService.clientConfiguration.post_logout_redirect_uri; openIDImplicitFlowConfiguration.start_checksession = this.oidcConfigService.clientConfiguration.start_checksession; openIDImplicitFlowConfiguration.silent_renew = this.oidcConfigService.clientConfiguration.silent_renew; openIDImplicitFlowConfiguration.post_login_route = this.oidcConfigService.clientConfiguration.startup_route; // HTTP 403 openIDImplicitFlowConfiguration.forbidden_route = this.oidcConfigService.clientConfiguration.forbidden_route; // HTTP 401 openIDImplicitFlowConfiguration.unauthorized_route = this.oidcConfigService.clientConfiguration.unauthorized_route; openIDImplicitFlowConfiguration.log_console_warning_active = this.oidcConfigService.clientConfiguration.log_console_warning_active; openIDImplicitFlowConfiguration.log_console_debug_active = this.oidcConfigService.clientConfiguration.log_console_debug_active; // id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time, // limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific. openIDImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = this.oidcConfigService.clientConfiguration.max_id_token_iat_offset_allowed_in_seconds; const authWellKnownEndpoints = new AuthWellKnownEndpoints(); authWellKnownEndpoints.setWellKnownEndpoints(this.oidcConfigService.wellKnownEndpoints); this.oidcSecurityService.setupModule(openIDImplicitFlowConfiguration, authWellKnownEndpoints); }); console.log('APP STARTING'); } }Create the login, logout component and use the oidcSecurityService
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; import { OidcSecurityService } from 'angular-auth-oidc-client'; @Component({ selector: 'my-app', templateUrl: 'app.component.html' }) export class AppComponent implements OnInit, OnDestroy { constructor(public oidcSecurityService: OidcSecurityService) { if (this.oidcSecurityService.moduleSetup) { this.doCallbackLogicIfRequired(); } else { this.oidcSecurityService.onModuleSetup.subscribe(() => { this.doCallbackLogicIfRequired(); }); } } ngOnInit() { } ngOnDestroy(): void { this.oidcSecurityService.onModuleSetup.unsubscribe(); } login() { this.oidcSecurityService.authorize(); } logout() { this.oidcSecurityService.logoff(); } private doCallbackLogicIfRequired() { if (window.location.hash) { this.oidcSecurityService.authorizedCallback(); } } }In the http services, add the token to the header using the oidcSecurityService
private setHeaders() { this.headers = new HttpHeaders(); this.headers = this.headers.set('Content-Type', 'application/json'); this.headers = this.headers.set('Accept', 'application/json'); const token = this._securityService.getToken(); if (token !== '') { const tokenValue = 'Bearer ' + token; this.headers = this.headers.set('Authorization', tokenValue); } }Loading the configuration from the server
Note the configuration json must return a property stsServer for this to work.
export function loadConfig(oidcConfigService: OidcConfigService) { console.log('APP_INITIALIZER STARTING'); return () => oidcConfigService.load(`${window.location.origin}/api/ClientAppSettings`); }Example:
You can add any configurations to this json, as long as the stsServer is present. This is REQUIRED. Then you can map the properties in the AppModule.
{ "stsServer":"https://localhost:44318", "redirect_url":"https://localhost:44311", "client_id":"angularclient", "response_type":"id_token token", "scope":"dataEventRecords securedFiles openid profile", "post_logout_redirect_uri":"https://localhost:44311", "start_checksession":true, "silent_renew":true, "startup_route":"/dataeventrecords", "forbidden_route":"/forbidden", "unauthorized_route":"/unauthorized", "log_console_warning_active":true, "log_console_debug_active":true, "max_id_token_iat_offset_allowed_in_seconds":"10", "apiServer":"https://localhost:44390/", "apiFileServer":"https://localhost:44378/" }Using without APP_INITIALIZER
export class AppModule { constructor( public oidcSecurityService: OidcSecurityService ) { const openIDImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration(); openIDImplicitFlowConfiguration.stsServer = 'https://localhost:44363'; openIDImplicitFlowConfiguration.redirect_url = 'https://localhost:44363'; // The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience. // The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client. openIDImplicitFlowConfiguration.client_id = 'singleapp'; openIDImplicitFlowConfiguration.response_type = 'id_token token'; openIDImplicitFlowConfiguration.scope = 'dataEventRecords openid'; openIDImplicitFlowConfiguration.post_logout_redirect_uri = 'https://localhost:44363/Unauthorized'; openIDImplicitFlowConfiguration.start_checksession = false; openIDImplicitFlowConfiguration.silent_renew = true; openIDImplicitFlowConfiguration.post_login_route = '/dataeventrecords'; // HTTP 403 openIDImplicitFlowConfiguration.forbidden_route = '/Forbidden'; // HTTP 401 openIDImplicitFlowConfiguration.unauthorized_route = '/Unauthorized'; openIDImplicitFlowConfiguration.log_console_warning_active = true; openIDImplicitFlowConfiguration.log_console_debug_active = true; // id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time, // limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific. openIDImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = 10; const authWellKnownEndpoints = new AuthWellKnownEndpoints(); authWellKnownEndpoints.issuer = 'https://localhost:44363'; authWellKnownEndpoints.jwks_uri = 'https://localhost:44363/.well-known/openid-configuration/jwks'; authWellKnownEndpoints.authorization_endpoint = 'https://localhost:44363/connect/authorize'; authWellKnownEndpoints.token_endpoint = 'https://localhost:44363/connect/token'; authWellKnownEndpoints.userinfo_endpoint = 'https://localhost:44363/connect/userinfo'; authWellKnownEndpoints.end_session_endpoint = 'https://localhost:44363/connect/endsession'; authWellKnownEndpoints.check_session_iframe = 'https://localhost:44363/connect/checksession'; authWellKnownEndpoints.revocation_endpoint = 'https://localhost:44363/connect/revocation'; authWellKnownEndpoints.introspection_endpoint = 'https://localhost:44363/connect/introspect'; this.oidcSecurityService.setupModule(openIDImplicitFlowConfiguration, authWellKnownEndpoints); } } Custom STS server well known configuration
Sometimes it is required to load custom .well-known/openid-configuration. The load_using_custom_stsServer can be used for this.
export function loadConfig(oidcConfigService: OidcConfigService) { console.log('APP_INITIALIZER STARTING'); return () => oidcConfigService.load_using_custom_stsServer('https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=b2c_1_susi'); }Using Guards
import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { map } from 'rxjs/operators'; import { OidcSecurityService } from './auth/services/oidc.security.service'; @Injectable() export class AuthorizationGuard implements CanActivate { constructor( private router: Router, private oidcSecurityService: OidcSecurityService ) { } public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean { console.log(route + '' + state); console.log('AuthorizationGuard, canActivate'); return this.oidcSecurityService.getIsAuthorized().pipe( map((isAuthorized: boolean) => { console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized); if (isAuthorized) { return true; } this.router.navigate(['/unauthorized']); return false; }) ); } } If you need, you can create a custom storage (for example to use cookies).
Implement OidcSecurityStorage class-interface and the read and write methods:
@Injectable() export class CustomStorage implements OidcSecurityStorage { public read(key: string): any { ... return ... } public write(key: string, value: any): void { ... } }Then provide the class in the module:
@NgModule({ imports: [ ... AuthModule.forRoot({ storage: CustomStorage }) ], ... })See also oidc.security.storage.ts for an example.
The HttpClient allows you to write interceptors. A common usecase would be to intercept any outgoing HTTP request and add an authorization header. Keep in mind that injecting OidcSecurityService into the interceptor via the constructor results in a cyclic dependency. To avoid this use the injector instead.
@Injectable() export class AuthInterceptor implements HttpInterceptor { private oidcSecurityService: OidcSecurityService; constructor(private injector: Injector) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let requestToForward = req; if (this.oidcSecurityService === undefined) { this.oidcSecurityService = this.injector.get(OidcSecurityService); } if (this.oidcSecurityService !== undefined) { let token = this.oidcSecurityService.getToken(); if (token !== "") { let tokenValue = "Bearer " + token; requestToForward = req.clone({ setHeaders: { "Authorization": tokenValue } }); } } else { console.debug("OidcSecurityService undefined: NO auth header!"); } return next.handle(requestToForward); } }https://github.com/damienbod/AspNetCoreAngularSignalRSecurity
https://github.com/damienbod/dotnet-template-angular
https://github.com/damienbod/angular-auth-oidc-sample-google-openid
https://github.com/HWouters/ad-b2c-oidc-angular
https://github.com/robisim74/angular-openid-connect-php/tree/angular-auth-oidc-client
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow
This npm package was created using the https://github.com/robisim74/angular-library-starter from Roberto Simonetti.
MIT
