Angular OpenID Connect Implicit Flow with IdentityServer4

This article shows how to implement an OpenID Connect Implicit Flow client in Angular. The Angular client is implemented in Typescript and uses IdentityServer4 and an ASP.NET core 1.1 resource server. The OpenID Connect specification for Implicit Flow can be found here.

Code: VS2017 msbuild

History:
2017.06.11: Updated to angular 4.2.1

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

Other posts in this series:

IdentityServer4 Configuration

The client configuration in IdentityServer4 is set up to use the enum Flow.Implicit and the required Angular client URLs. The RedirectUris must match the redirect_uri URL used for the client authorization request.

new Client
{
	ClientName = "angular2client",
	ClientId = "angular2client",
	AccessTokenType = AccessTokenType.Reference,
	//AccessTokenLifetime = 600, // 10 minutes, default 60 minutes
	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"
	}
}

Angular client using ASP.NET Core

Angular is downloaded from npm. The npm dependencies are defined in the package.json file. This file is hidden per default in Visual Studio. This can be made visible by adding DnxInvisibleContent Include=”package.json” to the project file. The required angular2 dependencies can be copied from the quickstart Angular guide on the Angular 2 web page. This changes regularly.

{
  "name": "angular-webpack-visualstudio",
  "version": "1.0.0",
  "description": "An Angular VS template",
  "main": "wwwroot/index.html",
  "author": "",
  "license": "ISC",
    "repository": {
    "type": "git",
    "url": "https://github.com/damienbod/Angular2WebpackVisualStudio.git"
  },
  "scripts": {
    "ngc": "ngc -p ./tsconfig-aot.json",
    "start": "concurrently \"webpack-dev-server --hot --inline --port 8080\" \"dotnet run\" ",
    "webpack-dev": "set NODE_ENV=development && webpack",
    "webpack-production": "set NODE_ENV=production && webpack",
    "build-dev": "npm run webpack-dev",
    "build-production": "npm run ngc && npm run webpack-production",
    "watch-webpack-dev": "set NODE_ENV=development && webpack --watch --color",
    "watch-webpack-production": "npm run build-production --watch --color",
    "publish-for-iis": "npm run build-production && dotnet publish -c Release"
  },
  "dependencies": {
    "@angular/common": "4.2.1",
    "@angular/compiler": "4.2.1",
    "@angular/compiler-cli": "4.2.1",
    "@angular/platform-server": "4.2.1",
    "@angular/core": "4.2.1",
    "@angular/forms": "4.2.1",
    "@angular/http": "4.2.1",
    "@angular/platform-browser": "4.2.1",
    "@angular/platform-browser-dynamic": "4.2.1",
    "@angular/router": "4.2.1",
    "@angular/upgrade": "4.2.1",
    "@angular/animations": "4.2.1",
    "angular-in-memory-web-api": "0.3.1",
    "core-js": "2.4.1",
    "reflect-metadata": "0.1.10",
    "rxjs": "5.3.0",
    "zone.js": "0.8.8",
    "bootstrap": "^3.3.7",
    "ie-shim": "~0.1.0"
  },
  "devDependencies": {
   "@types/node": "7.0.13",
    "@types/jasmine": "2.5.47",
    "angular2-template-loader": "0.6.2",
    "angular-router-loader": "^0.6.0",
    "awesome-typescript-loader": "3.1.2",
    "clean-webpack-plugin": "^0.1.16",
    "concurrently": "^3.4.0",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.28.0",
    "file-loader": "^0.11.1",
    "html-webpack-plugin": "^2.28.0",
    "jquery": "^3.2.1",
    "json-loader": "^0.5.4",
    "node-sass": "^4.5.2",
    "raw-loader": "^0.5.1",
    "rimraf": "^2.6.1",
    "sass-loader": "^6.0.3",
    "source-map-loader": "^0.2.1",
    "style-loader": "^0.16.1",
    "ts-helpers": "^1.1.2",
    "tslint": "^5.1.0",
    "tslint-loader": "^3.5.2",
    "typescript": "2.3.2",
    "url-loader": "^0.5.8",
    "webpack": "^2.4.1",
    "webpack-dev-server": "2.4.2",
    "jasmine-core": "2.5.2",
    "karma": "1.6.0",
    "karma-chrome-launcher": "2.0.0",
    "karma-jasmine": "1.1.0",
    "karma-sourcemap-loader": "0.3.7",
    "karma-spec-reporter": "0.0.31",
    "karma-webpack": "2.0.3"
  },
  "-vs-binding": {
    "ProjectOpened": [
      "watch-webpack-dev"
    ]
  }
}

The Typescript configuration for the project is defined in the tsconfig.json file in the root of the project. This is required to produce the js files.The tsconfig.json is configuration as follows for the development build.

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": true,
    "noImplicitAny": true,
    "skipLibCheck": true,
    "lib": [
      "es2015",
      "dom"
    ],
    "types": [
      "node"
    ]
  },
  "files": [
    "angularApp/app/app.module.ts",
    "angularApp/main.ts"
  ],
  "awesomeTypescriptLoaderOptions": {
    "useWebpackText": true
  },
  "compileOnSave": false,
  "buildOnSave": false
}

webpack is used to add the dependencies to the Angular2 app.

/// <binding ProjectOpened='Run - Development' />

var environment = (process.env.NODE_ENV || "development").trim();

if (environment === "development") {
    module.exports = require('./webpack.dev.js');
} else {
    module.exports = require('./webpack.prod.js');
}

The Angular application is then initialized in the index.html file in the angularApp folder. The required dependencies for Angular are added using webpack dist bundles. This could also be done for example using JSPM, but Visual Studio doesn’t work properly together with JSPM.

<!doctype html>
<html>
<head>
    <base href="./">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ASP.NET Core 1.0 Angular IdentityServer4 Client</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
	
    <script src="assets/jsrsasign.min.js"></script>
</head>
<body>
    <my-app>Loading...</my-app>
</body>
</html>

The vendor.ts in the app folder file is used to add the vendor npm packages to the application. The angular packages are not required here.

import 'jquery/src/jquery';
import 'bootstrap/dist/js/bootstrap';

import './css/bootstrap.css';
import './css/bootstrap-theme.css';

To make the angular application work with a F5 refresh in the browser, middleware needs to be added to the Startup class.

public void Configure(IApplicationBuilder app)
{
	app.UseIISPlatformHandler();

         var angularRoutes = new[] {
                "/forbidden",
                "/home",
                "/authorized",
                "/authorize",
                "/unauthorized",
                "/dataeventrecords",
                "/dataeventrecords/create",
                "/dataeventrecords/edit",
                "/logoff",
                "/securefile",
                "/securefile/securefiles",
        };

	app.Use(async (context, next) =>
	{
		if (context.Request.Path.HasValue &&
			null !=
			angularRoutes.FirstOrDefault(
			(ar) => context.Request.Path.Value.StartsWith(ar, StringComparison.OrdinalIgnoreCase)))
		{
			context.Request.Path = new PathString("/");
		}

		await next();
	});

	app.UseDefaultFiles();
	app.UseStaticFiles();

	app.Run(async (context) =>
	{
		await context.Response.WriteAsync("Hello World!");
	});
}

Angular Authorize

The Angular application is initialized in the main.ts file. This starts the AppModule defined in the app.module.ts file. The module bootstraps the AppComponent.

// Entry point for JiT compilation.
declare var System: any;

import './vendor';
import './polyfills';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

// Enables Hot Module Replacement.
declare var module: any;
if (module.hot) {
    module.hot.accept();
}

platformBrowserDynamic().bootstrapModule(AppModule);

The AppModule is the starting point for the application and loads all the required, child modules, componenets and services.

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { Configuration } from './app.constants';
import { routing } from './app.routes';
import { HttpModule, JsonpModule } from '@angular/http';

import { SecureFileService } from './securefile/SecureFileService';
import { DataEventRecordsService } from './dataeventrecords/DataEventRecordsService';
import { DataEventRecord } from './dataeventrecords/models/DataEventRecord';

import { ForbiddenComponent } from './forbidden/forbidden.component';
import { HomeComponent } from './home/home.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
import { SecureFilesComponent } from './securefile/securefiles.component';

import { DataEventRecordsListComponent } from './dataeventrecords/dataeventrecords-list.component';
import { DataEventRecordsCreateComponent } from './dataeventrecords/dataeventrecords-create.component';
import { DataEventRecordsEditComponent } from './dataeventrecords/dataeventrecords-edit.component';
import { AuthModule } from './auth/auth.module';
import { OidcSecurityService } from './auth/services/oidc.security.service';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        routing,
        HttpModule,
        JsonpModule,
        AuthModule.forRoot(),
    ],
    declarations: [
        AppComponent,
        ForbiddenComponent,
        HomeComponent,
        UnauthorizedComponent,
        SecureFilesComponent,
        DataEventRecordsListComponent,
        DataEventRecordsCreateComponent,
        DataEventRecordsEditComponent
    ],
    providers: [
        OidcSecurityService,
        SecureFileService,
        DataEventRecordsService,
        Configuration
    ],
    bootstrap:    [AppComponent],
})

export class AppModule {}

The authorization process is initialized in the AppComponent. The html defines the Login and the Logout buttons. The buttons are displayed using the securityService.IsAuthorized() which is set using the OidcSecurityService authorize process. the (click) definition is used to define the click event in Angular.

<div class="container" style="margin-top: 15px;">
    <!-- Static navbar -->
    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <div class="navbar-header">
                <button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a [routerLink]="['/dataeventrecords/list']" class="navbar-brand"><img src="assets/damienbod.jpg" height="40" style="margin-top:-10px;" /></a>
            </div>
            <div class="navbar-collapse collapse" id="navbar">
                <ul class="nav navbar-nav">
                    <li><a [routerLink]="['/dataeventrecords/list']">DataEventRecords</a></li>
                    <li><a [routerLink]="['/dataeventrecords/create']">Create DataEventRecord</a></li>
                    <li><a [routerLink]="['/securefile/securefiles']">Secured Files Download</a></li>

                    <li><a class="navigationLinkButton" *ngIf="!securityService.isAuthorized" (click)="login()">Login</a></li>
                    <li><a class="navigationLinkButton" *ngIf="securityService.isAuthorized" (click)="logout()">Logout</a></li>
                    <li><a class="navigationLinkButton" *ngIf="securityService.checkSessionChanged" (click)="refreshSession()">Refresh Session</a></li>
              
                </ul>
            </div><!--/.nav-collapse -->
        </div><!--/.container-fluid -->
    </nav>

    <router-outlet></router-outlet>

</div>

The app.component.ts defines the routes and the Login, Logout click events. The component uses the @Injectable() SecurityService where the authorization is implemented. It is important that the authorization callback is used outside of the angular routing as this removes the hash which is used to return the id_token. The ngOnInit method just checks if a hash exists, and if it does, executes the AuthorizedCallback method in the SecurityService.

import { Component, OnInit } from '@angular/core';
import { SecureFileService } from './SecureFileService';
import { OidcSecurityService } from '../auth/services/oidc.security.service';
import { Observable }       from 'rxjs/Observable';

@Component({
    selector: 'securefiles',
    templateUrl: 'securefiles.component.html',
    providers: [SecureFileService]
})

export class SecureFilesComponent implements OnInit {

    public message: string;
    public Files: string[];

    constructor(private _secureFileService: SecureFileService, public securityService: OidcSecurityService) {
        this.message = 'Secure Files download';
    }

    ngOnInit() {
      this.getData();
    }

    public DownloadFileById(id: any) {
        this._secureFileService.DownloadFile(id);
    }

    private getData() {
        this._secureFileService.GetListOfFiles()
            .subscribe(data => this.Files = data,
            error => this.securityService.handleError(error),
            () => console.log('getData for secure files, get all completed'));
    }
}

The new Angular routing is defined in the app.routes.ts file. The routing const is used in the AppModule imports. .

import { Routes, RouterModule } from '@angular/router';

import { ForbiddenComponent } from './forbidden/forbidden.component';
import { HomeComponent } from './home/home.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
import { SecureFilesComponent } from './securefile/securefiles.component';

import { DataEventRecordsListComponent } from './dataeventrecords/dataeventrecords-list.component';
import { DataEventRecordsCreateComponent } from './dataeventrecords/dataeventrecords-create.component';
import { DataEventRecordsEditComponent } from './dataeventrecords/dataeventrecords-edit.component';

const appRoutes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'home', component: HomeComponent },
    { path: 'Forbidden', component: ForbiddenComponent },
    { path: 'Unauthorized', component: UnauthorizedComponent },
    { path: 'securefile/securefiles', component: SecureFilesComponent },
    {
        path: 'dataeventrecords', component: DataEventRecordsListComponent
    },
    {
        path: 'dataeventrecords/create',
        component: DataEventRecordsCreateComponent
    },
    {
        path: 'dataeventrecords/edit/:id',
        component: DataEventRecordsEditComponent
    },
    {
        path: 'dataeventrecords/list',
        component: DataEventRecordsListComponent,
    }
];

export const routing = RouterModule.forRoot(appRoutes);

The The Authorization module in the angular app is configured in the auth.configuration.ts file using the AuthConfiguration class. The Oidc Implicit flow client is configured here, and must match the server configuration.

import { Injectable } from '@angular/core';

@Injectable()
export class AuthConfiguration {

    stsServer = 'https://localhost:44318';

    redirect_url = 'https://localhost:44311';

    // 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.
    client_id = 'angularclient';

    response_type = 'id_token token';

    scope = 'dataEventRecords securedFiles openid';

    post_logout_redirect_uri = 'https://localhost:44311/Unauthorized';

    start_checksession = true;

    silent_renew = true;

    startup_route = '/dataeventrecords/list';

    // HTTP 403
    forbidden_route = '/Forbidden';

    // HTTP 401
    unauthorized_route = '/Unauthorized';

    log_console_warning_active = true;

    log_console_debug_active = true;
}

The Authorize method calls the IdentityServer4 connect/authorize using a response type “id_token token”. This is one of the OpenID Connect Implicit flow which is described in the OpenID specification. The required parameters are also defined in this specification. Important is that the used parameters match the IdentityServer4 client definition.

authorize() {
	this.resetAuthorizationData();

	this.oidcSecurityCommon.logDebug('BEGIN Authorize, no auth data');

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

	this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_auth_state_control, state);
	this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_auth_nonce, nonce);
	this.oidcSecurityCommon.logDebug('AuthorizedController created. local state: ' + this.oidcSecurityCommon.retrieve(this.oidcSecurityCommon.storage_auth_state_control));

	let url = this.createAuthorizeUrl(nonce, state);
	window.location.href = url;
}

The user is redirected to the default IdentityServer4 login html view:

loginidentityserverwithaspnetidentity_01

And then to the permissions page, which shows what the client is requesting.

loginidentityserverwithaspnetidentity_02

Angular2 Authorize Callback

The AuthorizedCallback uses the returned hash to extract the token and the id_token and save these to the local storage of the browser. The method also checks the state and the nonce to prevent cross-site request forgery attacks.

authorizedCallback() {
	this.oidcSecurityCommon.logDebug('BEGIN AuthorizedCallback, no auth data');
	this.resetAuthorizationData();

	let hash = window.location.hash.substr(1);

	let result: any = hash.split('&').reduce(function (result: any, item: string) {
		let parts = item.split('=');
		result[parts[0]] = parts[1];
		return result;
	}, {});

	this.oidcSecurityCommon.logDebug(result);
	this.oidcSecurityCommon.logDebug('AuthorizedCallback created, begin token validation');

	let token = '';
	let id_token = '';
	let authResponseIsValid = false;

	this.getSigningKeys()
		.subscribe(jwtKeys => {
			this.jwtKeys = jwtKeys;

			if (!result.error) {

				// validate state
				if (this.oidcSecurityValidation.validateStateFromHashCallback(result.state, this.oidcSecurityCommon.retrieve(this.oidcSecurityCommon.storage_auth_state_control))) {
					token = result.access_token;
					id_token = result.id_token;
					let decoded: any;
					let headerDecoded;
					decoded = this.oidcSecurityValidation.getPayloadFromToken(id_token, false);
					headerDecoded = this.oidcSecurityValidation.getHeaderFromToken(id_token, false);

					// validate jwt signature
					if (this.oidcSecurityValidation.validate_signature_id_token(id_token, this.jwtKeys)) {
						// validate nonce
						if (this.oidcSecurityValidation.validate_id_token_nonce(decoded, this.oidcSecurityCommon.retrieve(this.oidcSecurityCommon.storage_auth_nonce))) {
							// validate iss
							if (this.oidcSecurityValidation.validate_id_token_iss(decoded, this.authWellKnownEndpoints.issuer)) {
								// validate aud
								if (this.oidcSecurityValidation.validate_id_token_aud(decoded, this.authConfiguration.client_id)) {
									// valiadate at_hash and access_token
									if (this.oidcSecurityValidation.validate_id_token_at_hash(token, decoded.at_hash) || !token) {
										this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_auth_nonce, '');
										this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_auth_state_control, '');

										authResponseIsValid = true;
										this.oidcSecurityCommon.logDebug('AuthorizedCallback state, nonce, iss, aud, signature validated, returning token');
									} else {
										this.oidcSecurityCommon.logWarning('AuthorizedCallback incorrect aud');
									}
								} else {
									this.oidcSecurityCommon.logWarning('AuthorizedCallback incorrect aud');
								}
							} else {
								this.oidcSecurityCommon.logWarning('AuthorizedCallback incorrect iss');
							}
						} else {
							this.oidcSecurityCommon.logWarning('AuthorizedCallback incorrect nonce');
						}
					} else {
						this.oidcSecurityCommon.logWarning('AuthorizedCallback incorrect Signature id_token');
					}
				} else {
					this.oidcSecurityCommon.logWarning('AuthorizedCallback incorrect state');
				}
			}

			if (authResponseIsValid) {
				this.setAuthorizationData(token, id_token);
				this.oidcSecurityUserService.initUserData()
					.subscribe(() => {
						this.onUserDataLoaded.emit();
						this.oidcSecurityCommon.logDebug(this.oidcSecurityCommon.retrieve(this.oidcSecurityCommon.storage_access_token));
						this.oidcSecurityCommon.logDebug(this.oidcSecurityUserService.userData);
						if (this.authConfiguration.start_checksession) {
							this.oidcSecurityCheckSession.init().subscribe(() => {
								this.oidcSecurityCheckSession.pollServerSession(result.session_state, this.authConfiguration.client_id);
							});
						}

						if (this.authConfiguration.silent_renew) {
							this.oidcSecuritySilentRenew.initRenew();
						}

						this.runTokenValidatation();

						this.router.navigate([this.authConfiguration.startup_route]);
					});

			} else {
				this.resetAuthorizationData();
				this.router.navigate([this.authConfiguration.unauthorized_route]);
			}
		});
}

The oidc.security.validation.ts is implemented as per spec from OpenID using the jsrsasign library.

import { Injectable } from '@angular/core';
import { OidcSecurityCommon } from './oidc.security.common';

// from jsrasiign
declare var KJUR: any;
declare var KEYUTIL: any;
declare var hextob64u: any;

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

// id_token
//// id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery) MUST exactly match the value of the iss (issuer) Claim.
//// id_token C2: 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.
// id_token C3: If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
// id_token C4: If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
//// id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the alg Header Parameter of the JOSE Header. The Client MUST use the keys provided by the Issuer.
//// id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the OpenID Connect Core 1.0 [OpenID.Core] specification.
//// id_token C7: The current time MUST be before the time represented by the exp Claim (possibly allowing for some small leeway to account for clock skew).
// 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.
//// id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one that was sent in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.The precise method for detecting replay attacks is Client specific.
// id_token C10: If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate.The meaning and processing of acr Claim Values is out of scope for this document.
// id_token C11: When a max_age request is made, the Client SHOULD check the auth_time Claim value and request re- authentication if it determines too much time has elapsed since the last End- User authentication.

//// Access Token Validation
//// access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA] for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
//// access_token C2: Take the left- most half of the hash and base64url- encode it.
//// access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash is present in the ID Token.

@Injectable()
export class OidcSecurityValidation {

    constructor(private oidcSecurityCommon: OidcSecurityCommon) {
    }

    // id_token C7: The current time MUST be before the time represented by the exp Claim (possibly allowing for some small leeway to account for clock skew).
    isTokenExpired(token: string, offsetSeconds?: number): boolean {

        let decoded: any;
        decoded = this.getPayloadFromToken(token, false);

        let tokenExpirationDate = this.getTokenExpirationDate(decoded);
        offsetSeconds = offsetSeconds || 0;

        if (tokenExpirationDate == null) {
            return false;
        }

        // Token expired?
        return !(tokenExpirationDate.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
    }

    // id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one that was sent in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.The precise method for detecting replay attacks is Client specific.
    validate_id_token_nonce(dataIdToken: any, local_nonce: any): boolean {
        if (dataIdToken.nonce !== local_nonce) {
            this.oidcSecurityCommon.logDebug('Validate_id_token_nonce failed, dataIdToken.nonce: ' + dataIdToken.nonce + ' local_nonce:' + local_nonce);
            return false;
        }

        return true;
    }

    // id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery) MUST exactly match the value of the iss (issuer) Claim.
    validate_id_token_iss(dataIdToken: any, client_id: any): boolean {
        if (dataIdToken.iss !== client_id) {
            this.oidcSecurityCommon.logDebug('Validate_id_token_iss failed, dataIdToken.iss: ' + dataIdToken.iss + ' client_id:' + client_id);
            return false;
        }

        return true;
    }

    // id_token C2: 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.
    validate_id_token_aud(dataIdToken: any, aud: any): boolean {
        if (dataIdToken.aud !== aud) {
            this.oidcSecurityCommon.logDebug('Validate_id_token_aud failed, dataIdToken.aud: ' + dataIdToken.aud + ' client_id:' + aud);
            return false;
        }

        return true;
    }

    validateStateFromHashCallback(state: any, local_state: any): boolean {
        if (state !== local_state) {
            this.oidcSecurityCommon.logDebug('ValidateStateFromHashCallback failed, state: ' + state + ' local_state:' + local_state);
            return false;
        }

        return true;
    }

    getPayloadFromToken(token: any, encode: boolean) {
        let data = {};
        if (typeof token !== 'undefined') {
            let encoded = token.split('.')[1];
            if (encode) {
                return encoded;
            }
            data = JSON.parse(this.urlBase64Decode(encoded));
        }

        return data;
    }

    getHeaderFromToken(token: any, encode: boolean) {
        let data = {};
        if (typeof token !== 'undefined') {
            let encoded = token.split('.')[0];
            if (encode) {
                return encoded;
            }
            data = JSON.parse(this.urlBase64Decode(encoded));
        }

        return data;
    }

    getSignatureFromToken(token: any, encode: boolean) {
        let data = {};
        if (typeof token !== 'undefined') {
            let encoded = token.split('.')[2];
            if (encode) {
                return encoded;
            }
            data = JSON.parse(this.urlBase64Decode(encoded));
        }

        return data;
    }

    // id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the alg Header Parameter of the JOSE Header. The Client MUST use the keys provided by the Issuer.
    // id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the OpenID Connect Core 1.0 [OpenID.Core] specification.
    validate_signature_id_token(id_token: any, jwtkeys: any): boolean {

        if (!jwtkeys || !jwtkeys.keys) {
            return false;
        }

        let header_data = this.getHeaderFromToken(id_token, false);
        let kid = header_data.kid;
        let alg = header_data.alg;

        if ('RS256' != alg) {
            this.oidcSecurityCommon.logWarning('Only RS256 supported');
            return false;
        }

        let isValid = false;

        for (let key of jwtkeys.keys) {
            if (key.kid === kid) {
                let publickey = KEYUTIL.getKey(key);
                isValid = KJUR.jws.JWS.verify(id_token, publickey, ['RS256']);
                return isValid;
            }
        }

        return isValid;
    }

    // Access Token Validation
    // access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA] for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
    // access_token C2: Take the left- most half of the hash and base64url- encode it.
    // access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash is present in the ID Token.
    validate_id_token_at_hash(access_token: any, at_hash: any): boolean {

        let hash = KJUR.crypto.Util.hashString(access_token, 'sha256');
        let first128bits = hash.substr(0, hash.length / 2);
        let testdata = hextob64u(first128bits);

        if (testdata === at_hash) {
            return true; // isValid;
        }

        return false;
    }

    private getTokenExpirationDate(dataIdToken: any): Date {
        if (!dataIdToken.hasOwnProperty('exp')) {
            return null;
        }

        let date = new Date(0); // The 0 here is the key, which sets the date to the epoch
        date.setUTCSeconds(dataIdToken.exp);

        return date;
    }

    private urlBase64Decode(str: string) {
        let output = str.replace('-', '+').replace('_', '/');
        switch (output.length % 4) {
            case 0:
                break;
            case 2:
                output += '==';
                break;
            case 3:
                output += '=';
                break;
            default:
                throw 'Illegal base64url string!';
        }

        return window.atob(output);
    }
}

Setting and reset the Authorization Data in the client

The received tokens and authorization data are saved or removed to the local storage and also the possible roles for the user. The application has only an ‘dataEventRecords.admin’ role or not. This matches the policy defined on the resource server.

private resetAuthorizationData() {
	this.isAuthorized = false;
	this.oidcSecurityCommon.resetStorageData();
	this.checkSessionChanged = false;
}

private setAuthorizationData(access_token: any, id_token: any) {
	if (this.oidcSecurityCommon.retrieve(this.oidcSecurityCommon.storage_access_token) !== '') {
		this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_access_token, '');
	}

	this.oidcSecurityCommon.logDebug(access_token);
	this.oidcSecurityCommon.logDebug(id_token);
	this.oidcSecurityCommon.logDebug('storing to storage, getting the roles');
	this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_access_token, access_token);
	this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_id_token, id_token);
	this.isAuthorized = true;
	this.oidcSecurityCommon.store(this.oidcSecurityCommon.storage_is_authorized, true);
}

private getUserData = (): Observable<string[]> => {
	this.setHeaders();
	return this._http.get('https://localhost:44318/connect/userinfo', {
		headers: this.headers,
		body: ''
	}).map(res => res.json());
}

private setHeaders() {
	this.headers = new Headers();
	this.headers.append('Content-Type', 'application/json');
	this.headers.append('Accept', 'application/json');

	var token = this.getToken();

	if (token !== "") {
		this.headers.append('Authorization', 'Bearer ' + token);
	}
}

The private retrieve and store methods are just used to store the data or get it from the local storage in the browser.

private retrieve(key: string): any {
	var item = this.storage.getItem(key);

	if (item && item !== 'undefined') {
		return JSON.parse(this.storage.getItem(key));
	}

	return;
}

private store(key: string, value: any) {
	this.storage.setItem(key, JSON.stringify(value));
}

Using the token to access the data

Now that the local storage has a token, this can then be used and added to the HTTP request headers. This is implemented in the DataEventRecordsService class. The setHeaders method is used to add the Authorization header with the token. Each request to the resource server uses this then.

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
import { Configuration } from '../app.constants';
import { OidcSecurityService } from '../auth/services/oidc.security.service';
import { DataEventRecord } from './models/DataEventRecord';

@Injectable()
export class DataEventRecordsService {

    private actionUrl: string;
    private headers: Headers;

    constructor(private _http: Http, private _configuration: Configuration, private _securityService: OidcSecurityService) {
        this.actionUrl = `${_configuration.Server}api/DataEventRecords/`;   
    }

    private setHeaders() {

        console.log("setHeaders started");

        this.headers = new Headers();
        this.headers.append('Content-Type', 'application/json');
        this.headers.append('Accept', 'application/json');

        var token = this._securityService.GetToken();
        if (token !== "") {
            let tokenValue = 'Bearer ' + token;
            console.log("tokenValue:" + tokenValue);
            this.headers.append('Authorization', tokenValue);
        }
    }

    public GetAll = (): Observable<DataEventRecord[]> => {
        this.setHeaders();
        let options = new RequestOptions({ headers: this.headers, body: '' });

        return this._http.get(this.actionUrl, options).map(res => res.json());
    }

    public GetById = (id: number): Observable<DataEventRecord> => {
        this.setHeaders();
        return this._http.get(this.actionUrl + id, {
            headers: this.headers,
            body: ''
        }).map(res => res.json());
    }

    public Add = (itemToAdd: any): Observable<Response> => {       
        this.setHeaders();
        return this._http.post(this.actionUrl, JSON.stringify(itemToAdd), { headers: this.headers });
    }

    public Update = (id: number, itemToUpdate: any): Observable<Response> => {
        this.setHeaders();
        return this._http
            .put(this.actionUrl + id, JSON.stringify(itemToUpdate), { headers: this.headers });
    }

    public Delete = (id: number): Observable<Response> => {
        this.setHeaders();
        return this._http.delete(this.actionUrl + id, {
            headers: this.headers
        });
    }

}

The token is then used to request the resource data and displays the secured data in the client application.

angular2_IdentityServer4_03

Using the roles, IsAuthorized check

The securityService.HasAdminRole is used to remove links and replace them with texts if the logged-in user has no claims to execute an edit entity. The delete button should also be disabled using this property, but this is left active to display the forbidden redirect with a 403.

<div class="col-md-12" *ngIf="securityService.isAuthorized" >
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">{{message}}</h3>
        </div>
        <div class="panel-body">
            <table class="table">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Timestamp</th>
                    </tr>
                </thead>
                <tbody>
                    <tr style="height:20px;" *ngFor="let dataEventRecord of DataEventRecords" >
                        <td>
                            <a *ngIf="hasAdminRole" href="" [routerLink]="['/dataeventrecords/edit/' + dataEventRecord.Id]" >{{dataEventRecord.Name}}</a>
                            <span *ngIf="!hasAdminRole">{{dataEventRecord.Name}}</span>
                        </td>
                        <td>{{dataEventRecord.Timestamp}}</td>
                        <td><button (click)="Delete(dataEventRecord.Id)">Delete</button></td>
                    </tr>
                </tbody>
            </table>

        </div>
    </div>
</div>

The private getData method uses the DataEventRecordsService to get the secured data. The SecurityService service is used to handle the errors from a server HTTP request to the resource server.

import { Component, OnInit } from '@angular/core';
import { OidcSecurityService } from '../auth/services/oidc.security.service';
import { Observable }       from 'rxjs/Observable';
import { Router } from '@angular/router';

import { DataEventRecordsService } from '../dataeventrecords/DataEventRecordsService';
import { DataEventRecord } from './models/DataEventRecord';

@Component({
    selector: 'dataeventrecords-list',
    templateUrl: 'dataeventrecords-list.component.html'
})

export class DataEventRecordsListComponent implements OnInit {

    public message: string;
    public DataEventRecords: DataEventRecord[];
   
    constructor(
        private _dataEventRecordsService: DataEventRecordsService,
        public securityService: OidcSecurityService,
        private _router: Router) {
        this.message = "DataEventRecords";
    }

    ngOnInit() {
        this.getData();
    }

    public Delete(id: any) {
        console.log("Try to delete" + id);
        this._dataEventRecordsService.Delete(id)
            .subscribe((() => console.log("subscribed")),
            error => this.securityService.HandleError(error),
            () => this.getData());
    }

    private getData() {
        console.log('DataEventRecordsListComponent:getData starting...');
        this._dataEventRecordsService
            .GetAll()
            .subscribe(data => this.DataEventRecords = data,
            error => this.securityService.HandleError(error),
            () => console.log('Get all completed'));
    }

}

Forbidden, Handle 401, 403 errors

The HandleError method in the SecurityService is used to check for a 401, 403 status in the response. If a 403 is returned, the user is redirected to the forbidden page. If a 401 is returned, the local user is reset and redirected to the Unauthorized route.

handleError(error: any) {
	this.oidcSecurityCommon.logError(error);
	if (error.status == 403) {
		this.router.navigate([this.authConfiguration.forbidden_route]);
	} else if (error.status == 401) {
		this.resetAuthorizationData();
		this.router.navigate([this.authConfiguration.unauthorized_route]);
	}
}

The redirect in the UI.

angular2_IdentityServer4_04

With Angular2 it’s not as simple to implement cross cutting concerns like authorization data for a logged in user. In angular, this was better as you could just use the $rootscope. It is also more difficult to debug, have still to figure out how to debug in visual studio with breakpoints.

Links

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

https://github.com/kjur/jsrsasign

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

http://www.codeproject.com/Articles/1087605/Angular-typescript-configuration-and-debugging-for

Announcing IdentityServer for ASP.NET 5 and .NET Core

https://github.com/IdentityServer/IdentityServer4

https://github.com/IdentityServer/IdentityServer4.Samples

The State of Security in ASP.NET 5 and MVC 6: OAuth 2.0, OpenID Connect and IdentityServer

http://connect2id.com/learn/openid-connect

https://github.com/FabianGosebrink/Angular2-ASPNETCore-SignalR-Demo

Getting Started with ASP NET Core 1 and Angular 2 in Visual Studio 2015

http://benjii.me/2016/01/angular2-routing-with-asp-net-core-1/

http://tattoocoder.azurewebsites.net/angular2-aspnet5-spa-template/

Cross-platform Single Page Applications with ASP.NET Core 1.0, Angular 2 & TypeScript

https://angular.io/docs/ts/latest/guide/router.html

Advertisements

129 comments

  1. Manjit singh · · Reply

    And when i run npm run buildProduction in cmd then its giving following log:
    0 info it worked if it ends with ok
    1 verbose cli [ ‘C:\\Program Files\\nodejs\\node.exe’,
    1 verbose cli ‘C:\\Users\\manjit.singh\\AppData\\Roaming\\npm\\node_modules\\npm\\bin\\npm-cli.js’,
    1 verbose cli ‘run’,
    1 verbose cli ‘ngc’ ]
    2 info using npm@4.5.0
    3 info using node@v7.9.0
    4 verbose run-script [ ‘prengc’, ‘ngc’, ‘postngc’ ]
    5 info lifecycle @1.0.0~prengc: @1.0.0
    6 silly lifecycle @1.0.0~prengc: no script for prengc, continuing
    7 info lifecycle @1.0.0~ngc: @1.0.0
    8 verbose lifecycle @1.0.0~ngc: unsafe-perm in lifecycle true
    9 verbose lifecycle @1.0.0~ngc: PATH: C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\bin\node-gyp-bin;D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client\node_modules\.bin;C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\bin\node-gyp-bin;D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client\node_modules\.bin;C:\Program Files\Common Files\Microsoft Shared\Microsoft Online Services;C:\Program Files (x86)\Common Files\Microsoft Shared\Microsoft Online Services;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\110\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\ManagementStudio\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Program Files\dotnet\;C:\Program Files (x86)\Skype\Phone\;C:\Program Files\nodejs\;C:\Program Files (x86)\Microsoft VS Code\bin;C:\Users\manjit.singh\AppData\Roaming\npm
    10 verbose lifecycle @1.0.0~ngc: CWD: D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client
    11 silly lifecycle @1.0.0~ngc: Args: [ ‘/d /s /c’, ‘ngc -p ./tsconfig-aot.json’ ]
    12 silly lifecycle @1.0.0~ngc: Returned: code: 1 signal: null
    13 info lifecycle @1.0.0~ngc: Failed to exec ngc script
    14 verbose stack Error: @1.0.0 ngc: `ngc -p ./tsconfig-aot.json`
    14 verbose stack Exit status 1
    14 verbose stack at EventEmitter. (C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\lib\utils\lifecycle.js:279:16)
    14 verbose stack at emitTwo (events.js:106:13)
    14 verbose stack at EventEmitter.emit (events.js:194:7)
    14 verbose stack at ChildProcess. (C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\lib\utils\spawn.js:40:14)
    14 verbose stack at emitTwo (events.js:106:13)
    14 verbose stack at ChildProcess.emit (events.js:194:7)
    14 verbose stack at maybeClose (internal/child_process.js:899:16)
    14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5)
    15 verbose pkgid @1.0.0
    16 verbose cwd D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client
    17 verbose Windows_NT 6.3.9600
    18 verbose argv “C:\\Program Files\\nodejs\\node.exe” “C:\\Users\\manjit.singh\\AppData\\Roaming\\npm\\node_modules\\npm\\bin\\npm-cli.js” “run” “ngc”
    19 verbose node v7.9.0
    20 verbose npm v4.5.0
    21 error code ELIFECYCLE
    22 error errno 1
    23 error @1.0.0 ngc: `ngc -p ./tsconfig-aot.json`
    23 error Exit status 1
    24 error Failed at the @1.0.0 ngc script ‘ngc -p ./tsconfig-aot.json’.
    24 error Make sure you have the latest version of node.js and npm installed.
    24 error If you do, this is most likely a problem with the package,
    24 error not with npm itself.
    24 error Tell the author that this fails on your system:
    24 error ngc -p ./tsconfig-aot.json
    24 error You can get information on how to open an issue for this project with:
    24 error npm bugs
    24 error Or if that isn’t available, you can get their info via:
    24 error npm owner ls
    24 error There is likely additional logging output above.
    25 verbose exit [ 1, true ]

    1. Manjit singh · · Reply

      And i have the latest version of node.js installed on my machine

  2. error Failed at the @1.0.0 ngc script ‘ngc -p ./tsconfig-aot.json’

    Is ngc installed and running from the command line?

    1. Manjit Singh · · Reply

      How can i install and run this?

      1. npm install ngc -g

      2. Manjit singh · ·

        I have installed this now its giving following error:

        0 info it worked if it ends with ok
        1 verbose cli [ ‘C:\\Program Files\\nodejs\\node.exe’,
        1 verbose cli ‘C:\\Users\\manjit.singh\\AppData\\Roaming\\npm\\node_modules\\npm\\bin\\npm-cli.js’,
        1 verbose cli ‘run’,
        1 verbose cli ‘ngc’ ]
        2 info using npm@4.5.0
        3 info using node@v7.9.0
        4 verbose run-script [ ‘prengc’, ‘ngc’, ‘postngc’ ]
        5 info lifecycle @1.0.0~prengc: @1.0.0
        6 silly lifecycle @1.0.0~prengc: no script for prengc, continuing
        7 info lifecycle @1.0.0~ngc: @1.0.0
        8 verbose lifecycle @1.0.0~ngc: unsafe-perm in lifecycle true
        9 verbose lifecycle @1.0.0~ngc: PATH: C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\bin\node-gyp-bin;D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client\node_modules\.bin;C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\bin\node-gyp-bin;D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client\node_modules\.bin;C:\Program Files\Common Files\Microsoft Shared\Microsoft Online Services;C:\Program Files (x86)\Common Files\Microsoft Shared\Microsoft Online Services;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\110\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\ManagementStudio\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Program Files\dotnet\;C:\Program Files (x86)\Skype\Phone\;C:\Program Files\nodejs\;C:\Program Files (x86)\Microsoft VS Code\bin;C:\Users\manjit.singh\AppData\Roaming\npm
        10 verbose lifecycle @1.0.0~ngc: CWD: D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client
        11 silly lifecycle @1.0.0~ngc: Args: [ ‘/d /s /c’, ‘ngc -p ./tsconfig-aot.json’ ]
        12 silly lifecycle @1.0.0~ngc: Returned: code: 1 signal: null
        13 info lifecycle @1.0.0~ngc: Failed to exec ngc script
        14 verbose stack Error: @1.0.0 ngc: `ngc -p ./tsconfig-aot.json`
        14 verbose stack Exit status 1
        14 verbose stack at EventEmitter. (C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\lib\utils\lifecycle.js:279:16)
        14 verbose stack at emitTwo (events.js:106:13)
        14 verbose stack at EventEmitter.emit (events.js:194:7)
        14 verbose stack at ChildProcess. (C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\lib\utils\spawn.js:40:14)
        14 verbose stack at emitTwo (events.js:106:13)
        14 verbose stack at ChildProcess.emit (events.js:194:7)
        14 verbose stack at maybeClose (internal/child_process.js:899:16)
        14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5)
        15 verbose pkgid @1.0.0
        16 verbose cwd D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client
        17 verbose Windows_NT 6.3.9600
        18 verbose argv “C:\\Program Files\\nodejs\\node.exe” “C:\\Users\\manjit.singh\\AppData\\Roaming\\npm\\node_modules\\npm\\bin\\npm-cli.js” “run” “ngc”
        19 verbose node v7.9.0
        20 verbose npm v4.5.0
        21 error code ELIFECYCLE
        22 error errno 1
        23 error @1.0.0 ngc: `ngc -p ./tsconfig-aot.json`
        23 error Exit status 1
        24 error Failed at the @1.0.0 ngc script ‘ngc -p ./tsconfig-aot.json’.
        24 error Make sure you have the latest version of node.js and npm installed.
        24 error If you do, this is most likely a problem with the package,
        24 error not with npm itself.
        24 error Tell the author that this fails on your system:
        24 error ngc -p ./tsconfig-aot.json
        24 error You can get information on how to open an issue for this project with:
        24 error npm bugs
        24 error Or if that isn’t available, you can get their info via:
        24 error npm owner ls
        24 error There is likely additional logging output above.
        25 verbose exit [ 1, true ]

        2nd log file:

        0 info it worked if it ends with ok
        1 verbose cli [ ‘C:\\Program Files\\nodejs\\node.exe’,
        1 verbose cli ‘C:\\Users\\manjit.singh\\AppData\\Roaming\\npm\\node_modules\\npm\\bin\\npm-cli.js’,
        1 verbose cli ‘run’,
        1 verbose cli ‘buildProduction’ ]
        2 info using npm@4.5.0
        3 info using node@v7.9.0
        4 verbose run-script [ ‘prebuildProduction’,
        4 verbose run-script ‘buildProduction’,
        4 verbose run-script ‘postbuildProduction’ ]
        5 info lifecycle @1.0.0~prebuildProduction: @1.0.0
        6 silly lifecycle @1.0.0~prebuildProduction: no script for prebuildProduction, continuing
        7 info lifecycle @1.0.0~buildProduction: @1.0.0
        8 verbose lifecycle @1.0.0~buildProduction: unsafe-perm in lifecycle true
        9 verbose lifecycle @1.0.0~buildProduction: PATH: C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\bin\node-gyp-bin;D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client\node_modules\.bin;C:\Program Files\Common Files\Microsoft Shared\Microsoft Online Services;C:\Program Files (x86)\Common Files\Microsoft Shared\Microsoft Online Services;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\110\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\ManagementStudio\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Program Files\dotnet\;C:\Program Files (x86)\Skype\Phone\;C:\Program Files\nodejs\;C:\Program Files (x86)\Microsoft VS Code\bin;C:\Users\manjit.singh\AppData\Roaming\npm
        10 verbose lifecycle @1.0.0~buildProduction: CWD: D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client
        11 silly lifecycle @1.0.0~buildProduction: Args: [ ‘/d /s /c’, ‘npm run ngc && npm run webpack-prod’ ]
        12 silly lifecycle @1.0.0~buildProduction: Returned: code: 1 signal: null
        13 info lifecycle @1.0.0~buildProduction: Failed to exec buildProduction script
        14 verbose stack Error: @1.0.0 buildProduction: `npm run ngc && npm run webpack-prod`
        14 verbose stack Exit status 1
        14 verbose stack at EventEmitter. (C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\lib\utils\lifecycle.js:279:16)
        14 verbose stack at emitTwo (events.js:106:13)
        14 verbose stack at EventEmitter.emit (events.js:194:7)
        14 verbose stack at ChildProcess. (C:\Users\manjit.singh\AppData\Roaming\npm\node_modules\npm\lib\utils\spawn.js:40:14)
        14 verbose stack at emitTwo (events.js:106:13)
        14 verbose stack at ChildProcess.emit (events.js:194:7)
        14 verbose stack at maybeClose (internal/child_process.js:899:16)
        14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5)
        15 verbose pkgid @1.0.0
        16 verbose cwd D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client
        17 verbose Windows_NT 6.3.9600
        18 verbose argv “C:\\Program Files\\nodejs\\node.exe” “C:\\Users\\manjit.singh\\AppData\\Roaming\\npm\\node_modules\\npm\\bin\\npm-cli.js” “run” “buildProduction”
        19 verbose node v7.9.0
        20 verbose npm v4.5.0
        21 error code ELIFECYCLE
        22 error errno 1
        23 error @1.0.0 buildProduction: `npm run ngc && npm run webpack-prod`
        23 error Exit status 1
        24 error Failed at the @1.0.0 buildProduction script ‘npm run ngc && npm run webpack-prod’.
        24 error Make sure you have the latest version of node.js and npm installed.
        24 error If you do, this is most likely a problem with the package,
        24 error not with npm itself.
        24 error Tell the author that this fails on your system:
        24 error npm run ngc && npm run webpack-prod
        24 error You can get information on how to open an issue for this project with:
        24 error npm bugs
        24 error Or if that isn’t available, you can get their info via:
        24 error npm owner ls
        24 error There is likely additional logging output above.
        25 verbose exit [ 1, true ]

      3. Manjit singh · ·

        I think its giving the same error 😦

  3. Andrew · · Reply

    How to refresh token ? I’ve gotten access_token, id_token, but dont understand how to refresh access_token.
    There is no refresh_token in result.
    let result: any = hash.split(‘&’).reduce(function (result: any, item: string) {
    let parts = item.split(‘=’);
    result[parts[0]] = parts[1];
    return result;
    }, {});

    1. You need to implement openId session management for this if using this flow. I plan to do this when I get time.

      Greetings Damien

      1. Andrew · ·

        Thanks for quick answer

  4. […] Angular 2 with OpenID Connect Implicit Flow from Damien Bowden […]

  5. Great tutorial! The thing I’m a bit concerned about is using jsrsasign. It’s a massive library and worst of all, includes a version of yui from 2011, which apparently manages to hook itself into all kinds of places, thus slowing down the entire site it’s used in. Have you come across anything that could be used to replace jsrsasign? I just recently started looking into this myself… Of course, I could just rely on the server side to validate the token, which it will do anyway, but it would be nice to do some checking on the client side as well.

    1. Thanks, I have done very little research into jsrasign, thanks for this info, very useful. Just one note, you must validate the signature of the token and also the token on the client, without the client side validation, the application is way less secure. Validating on the server is no help. I will research into this further, or if you find an replacement, please let me know.

      Greetings Damien

      1. You’re right, of course. Looks like Auth0 (unsurprisingly) has several packages that may be suitable for replacing jsrsasign for the JWT stuff. Their https://github.com/auth0/idtoken-verifier looks like it might work, or even https://github.com/auth0/angular-jwt for a more complete solution.

  6. […] ANGULAR OPENID CONNECT IMPLICIT FLOW WITH IDENTITYSERVER4 ASP.NET Core & Angular2 + OpenID Connect using Visual Studio Code Repo for the previous link Repo for with example Angular OidcClient […]

  7. […] Angular OpenID Connect Implicit Flow with IdentityServer4 […]

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: