angular-auth-oidc-client
OpenID Connect Implicit Flow
OpenID Certification
This library is certified by OpenID Foundation. (RP Implicit and Config RP)
Features
Documentation : Quickstart | API Documentation | Changelog
Using the package
Navigate to the level of your package.json and type
npm install angular-auth-oidc-client --save
or with yarn
yarn add angular-auth-oidc-client
or you can add the npm package to your package.json
"angular-auth-oidc-client": "4.0.1"
and type
npm install
Using in the angular application
Import 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;
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;
openIDImplicitFlowConfiguration.forbidden_route = this.oidcConfigService.clientConfiguration.forbidden_route;
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;
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';
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';
openIDImplicitFlowConfiguration.forbidden_route = '/Forbidden';
openIDImplicitFlowConfiguration.unauthorized_route = '/Unauthorized';
openIDImplicitFlowConfiguration.log_console_warning_active = true;
openIDImplicitFlowConfiguration.log_console_debug_active = true;
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;
})
);
}
}
Custom Storage
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.
Http Interceptor
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);
}
}
Examples using:
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
Using src code directly:
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow
Notes:
This npm package was created using the https://github.com/robisim74/angular-library-starter from Roberto Simonetti.
License
MIT