SDohle / angular-auth-oidc-client

npm package for OpenID Connect Implicit Flow

Home Page:https://www.npmjs.com/package/angular-auth-oidc-client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

angular-auth-oidc-client

Build Status npm npm npm

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;
            // 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;
            })
        );
    }
}

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

About

npm package for OpenID Connect Implicit Flow

https://www.npmjs.com/package/angular-auth-oidc-client

License:MIT License


Languages

Language:TypeScript 95.1%Language:JavaScript 4.9%