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 2.0 resource server. The OpenID Connect specification for Implicit Flow can be found here.
Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow
History:
2019-09-20: Updated ASP.NET Core 3.0, Angular 8.2.6
2018-12-05: Updated to ASP.NET Core 2.2 Angular 7.1.1
2018-06-22: Updated ASP.NET Core 2.1, Angular 6.0.6, ASP.NET Core Identity 2.1
Full history:
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow#history
Other posts in this series:
- Authorization Policies and Data Protection with IdentityServer4 in ASP.NET Core
- Angular OpenID Connect Implicit Flow with IdentityServer4
- Angular Secure File Download without using an access token in URL or cookies
- Full Server logout with IdentityServer4 and OpenID Connect Implicit Flow
- IdentityServer4, WebAPI and Angular in a single ASP.NET Core project
- Extending Identity in IdentityServer4 to manage users in ASP.NET Core
- Implementing a silent token renew in Angular for the OpenID Connect Implicit flow
- OpenID Connect Session Management using an Angular application and IdentityServer4
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 = "angularclient", ClientId = "angularclient", 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": "5.0.22", "description": "An Angular VS template", "main": "wwwroot/index.html", "author": "", "license": "ISC", "repository": { "type": "git", "url": "https://github.com/damienbod/Angular2WebpackVisualStudio.git" }, "scripts": { "start": "concurrently \"webpack-dev-server --env=dev --open --hot --inline --port 8080\" \"dotnet run\" ", "webpack-dev": "webpack --env=dev", "webpack-production": "webpack --env=prod", "build": "npm run webpack-dev", "build-dev": "npm run webpack-dev", "build-production": "npm run webpack-production", "watch-webpack-dev": "webpack --env=dev --watch --color", "watch-webpack-production": "npm run build-production --watch --color", "publish-for-iis": "npm run build-production && dotnet publish -c Release", "test": "karma start", "test-ci": "karma start --single-run --browsers ChromeHeadless", "lint": "tslint -p tsconfig.json angularApp/**/*.ts" }, "dependencies": { "@angular/animations": "~8.2.6", "@angular/common": "~8.2.6", "@angular/compiler": "~8.2.6", "@angular/core": "~8.2.6", "@angular/forms": "~8.2.6", "@angular/http": "~7.2.15", "angular-l10n": "^8.1.2", "@angular/platform-browser": "~8.2.6", "@angular/platform-browser-dynamic": "~8.2.6", "@angular/router": "~8.2.6", "bootstrap": "4.3.1", "core-js": "^2.6.5", "ie-shim": "0.1.0", "jsrsasign": "8.0.12", "popper.js": "^1.15.0", "rxjs": "~6.5.3", "rxjs-compat": "^6.5.3", "zone.js": "~0.10.2" }, "devDependencies": { "@angular-devkit/build-angular": "~0.803.4", "@angular/cli": "~8.3.4", "@angular/compiler-cli": "~8.2.6", "@angular/language-service": "~8.2.6", "@types/node": "~12.7.5", "@types/jasmine": "~3.4.0", "@types/jasminewd2": "~2.0.6", "codelyzer": "~5.1.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~4.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "~2.1.0", "karma-jasmine": "~2.0.1", "@ngtools/webpack": "^6.2.5", "typescript": "~3.4.5", "karma-jasmine-html-reporter": "^1.4.2", "protractor": "~6.0.0", "ts-node": "~8.3.0", "tslint": "~5.20.0", "angular-router-loader": "0.8.5", "angular2-template-loader": "^0.6.2", "awesome-typescript-loader": "^5.2.1", "clean-webpack-plugin": "^2.0.2", "concurrently": "^4.1.0", "copy-webpack-plugin": "^5.0.3", "css-loader": "^2.1.1", "file-loader": "^3.0.1", "html-webpack-plugin": "^3.2.0", "jquery": "^3.4.1", "json-loader": "^0.5.7", "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "^0.0.32", "karma-webpack": "3.0.5", "raw-loader": "^1.0.0", "node-sass": "^4.12.0", "rimraf": "^2.6.3", "sass-loader": "^7.1.0", "source-map-loader": "^0.2.4", "style-loader": "^0.23.1", "tslint-loader": "^3.5.4", "toposort": "2.0.2", "uglifyjs-webpack-plugin": "^2.1.3", "url-loader": "^1.1.2", "webpack": "^4.40.2", "webpack-bundle-analyzer": "^3.5.0", "webpack-cli": "3.3.8", "webpack-dev-server": "^3.8.0", "webpack-filter-warnings-plugin": "^1.2.1" }, "-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, "noUnusedLocals": true, "noUnusedParameters": true, "lib": [ "es2015", "dom" ], "typeRoots": [ "./node_modules/@types/" ] }, "exclude": [ "node_modules", "./angularApp/main-aot.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.
<!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 3.0 Angular Implicit Flow IdentityServer4 Client</title> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <body> <app-component>Loading...</app-component> </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/css/bootstrap.css'; import 'bootstrap/dist/js/bootstrap.bundle.min.js'; // Other vendors for example jQuery, Lodash or Bootstrap // You can import js, ts, css, sass, ...
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.UseCors("AllowAllOrigins"); var angularRoutes = new[] { "/home", "/forbidden", "/authorized", "/authorize", "/unauthorized", "/dataeventrecords", "/dataeventrecords/list", "/dataeventrecords/create", "/dataeventrecords/edit", "/logoff", "/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.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
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.
import './styles.scss'; import 'zone.js/dist/zone'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; // import { platformBrowser } from '@angular/platform-browser'; import { AppModule } from './app/app.module'; // Styles. // 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, components and services.
import { NgModule, APP_INITIALIZER } 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 { HttpClientModule } from '@angular/common/http'; import { SecureFileService } from './securefile/SecureFileService'; 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 { DataEventRecordsModule } from './dataeventrecords/dataeventrecords.module'; import { AuthModule } from './auth/modules/auth.module'; import { OidcSecurityService } from './auth/services/oidc.security.service'; import { OidcConfigService, ConfigResult } from './auth/services/oidc.security.config.service'; import { OpenIdConfiguration } from './auth/models/auth.configuration'; import { L10nConfig, L10nLoader, TranslationModule, StorageStrategy, ProviderType } from 'angular-l10n'; import { AuthorizationGuard } from './authorization.guard'; import { AuthorizationCanGuard } from './authorization.can.guard'; const l10nConfig: L10nConfig = { locale: { languages: [ { code: 'en', dir: 'ltr' }, { code: 'it', dir: 'ltr' }, { code: 'fr', dir: 'ltr' }, { code: 'de', dir: 'ltr' } ], language: 'en', storage: StorageStrategy.Cookie }, translation: { providers: [ { type: ProviderType.Static, prefix: './i18n/locale-' } ], caching: true, missingValue: 'No key' } }; export function loadConfig(oidcConfigService: OidcConfigService) { console.log('APP_INITIALIZER STARTING'); return () => oidcConfigService.load(`${window.location.origin}/api/ClientAppSettings`); } @NgModule({ imports: [ BrowserModule, FormsModule, routing, HttpClientModule, TranslationModule.forRoot(l10nConfig), DataEventRecordsModule, AuthModule.forRoot(), ], declarations: [ AppComponent, ForbiddenComponent, HomeComponent, UnauthorizedComponent, SecureFilesComponent ], providers: [ OidcConfigService, OidcSecurityService, { provide: APP_INITIALIZER, useFactory: loadConfig, deps: [OidcConfigService], multi: true }, AuthorizationGuard, AuthorizationCanGuard, SecureFileService, Configuration ], bootstrap: [AppComponent], }) export class AppModule { constructor( private oidcSecurityService: OidcSecurityService, private oidcConfigService: OidcConfigService, configuration: Configuration, public l10nLoader: L10nLoader ) { this.l10nLoader.load(); this.oidcConfigService.onConfigurationLoaded.subscribe((configResult: ConfigResult) => { const config: OpenIdConfiguration = { stsServer: configResult.customConfig.stsServer, redirect_url: configResult.customConfig.redirect_url, client_id: configResult.customConfig.client_id, response_type: configResult.customConfig.response_type, scope: configResult.customConfig.scope, post_logout_redirect_uri: configResult.customConfig.post_logout_redirect_uri, start_checksession: configResult.customConfig.start_checksession, silent_renew: configResult.customConfig.silent_renew, silent_renew_url: configResult.customConfig.redirect_url + '/silent-renew.html', post_login_route: configResult.customConfig.startup_route, forbidden_route: configResult.customConfig.forbidden_route, unauthorized_route: configResult.customConfig.unauthorized_route, log_console_warning_active: configResult.customConfig.log_console_warning_active, log_console_debug_active: configResult.customConfig.log_console_debug_active, max_id_token_iat_offset_allowed_in_seconds: configResult.customConfig.max_id_token_iat_offset_allowed_in_seconds, history_cleanup_off: true // iss_validation_off: false // disable_iat_offset_validation: true }; configuration.FileServer = configResult.customConfig.apiFileServer; configuration.Server = configResult.customConfig.apiServer; this.oidcSecurityService.setupModule(config, configResult.authWellknownEndpoints); }); console.log('APP STARTING'); } }
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"> <!-- Static navbar --> <nav class="bg-dark mb-4 navbar navbar-dark navbar-expand-md"> <div class="container-fluid"> <div class="navbar-header"> <a [routerLink]="['/dataeventrecords']" 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="mr-auto navbar-nav"> <li class="nav-item"><a class="nav-link" *ngIf="isAuthorized" [routerLink]="['/dataeventrecords']">DataEventRecords</a></li> <li class="nav-item"><a class="nav-link" *ngIf="isAuthorized" [routerLink]="['/dataeventrecords/create']">Create DataEventRecord</a></li> <li class="nav-item"><a class="nav-link" *ngIf="isAuthorized" [routerLink]="['/securefiles']">Secured Files Download</a></li> <li class="nav-item"><a class="nav-link" *ngIf="!isAuthorized" (click)="login()">{{ 'LOGIN' | translate:lang }}</a></li> <li class="nav-item"><a class="nav-link" *ngIf="isAuthorized" (click)="logout()">{{ 'LOGOUT' | translate:lang }}</a></li> <li class="nav-item"><a class="nav-link" (click)="changeCulture('de','CH')">DE</a></li> <li class="nav-item"><a class="nav-link" (click)="changeCulture('fr','CH')">FR</a></li> <li class="nav-item"><a class="nav-link" (click)="changeCulture('it','CH')">IT</a></li> <li class="nav-item"><a class="nav-link" (click)="changeCulture('en','US')">EN</a></li> <li class="nav-item"><a class="nav-link" *ngIf="checksession" (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, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { Router } from '@angular/router'; import { OidcSecurityService } from './auth/services/oidc.security.service'; import { LocaleService, TranslationService, Language } from 'angular-l10n'; import './app.component.css'; import { AuthorizationResult } from './auth/models/authorization-result'; import { AuthorizationState } from './auth/models/authorization-state.enum'; // import { ValidationResult } from './auth/models/validation-result.enum'; @Component({ selector: 'app-component', templateUrl: 'app.component.html', }) export class AppComponent implements OnInit, OnDestroy { @Language() lang = ''; title = ''; isAuthorizedSubscription: Subscription | undefined; isAuthorized = false; onChecksessionChanged: Subscription | undefined; checksession = false; constructor( public oidcSecurityService: OidcSecurityService, public locale: LocaleService, private router: Router, public translation: TranslationService ) { console.log('AppComponent STARTING'); if (this.oidcSecurityService.moduleSetup) { this.doCallbackLogicIfRequired(); } else { this.oidcSecurityService.onModuleSetup.subscribe(() => { this.doCallbackLogicIfRequired(); }); } this.oidcSecurityService.onCheckSessionChanged.subscribe( (checksession: boolean) => { console.log('...recieved a check session event'); this.checksession = checksession; }); this.oidcSecurityService.onAuthorizationResult.subscribe( (authorizationResult: AuthorizationResult) => { this.onAuthorizationResultComplete(authorizationResult); }); } ngOnInit() { this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe( (isAuthorized: boolean) => { this.isAuthorized = isAuthorized; }); } changeCulture(language: string, country: string) { this.locale.setDefaultLocale(language, country); console.log('set language: ' + language); } ngOnDestroy(): void { if (this.isAuthorizedSubscription) { this.isAuthorizedSubscription.unsubscribe(); } } login() { console.log('start login'); let culture = 'de-CH'; if (this.locale.getCurrentCountry()) { culture = this.locale.getCurrentLanguage() + '-' + this.locale.getCurrentCountry(); } this.oidcSecurityService.setCustomRequestParameters({ 'ui_locales': culture}); this.oidcSecurityService.authorize(); } refreshSession() { console.log('start refreshSession'); this.oidcSecurityService.authorize(); } logout() { console.log('start logoff'); this.oidcSecurityService.logoff(); } private doCallbackLogicIfRequired() { if (window.location.hash) { this.oidcSecurityService.authorizedImplicitFlowCallback(); } } private onAuthorizationResultComplete(authorizationResult: AuthorizationResult) { console.log('Auth result received AuthorizationState:' + authorizationResult.authorizationState + ' validationResult:' + authorizationResult.validationResult); this.oidcSecurityService.getUserData().subscribe( (data: any) => { console.log(data); }); if (authorizationResult.authorizationState === AuthorizationState.unauthorized) { if (window.parent) { // sent from the child iframe, for example the silent renew this.router.navigate(['/unauthorized']); } else { window.location.href = '/unauthorized'; } } } }
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 { AuthorizationGuard } from './authorization.guard'; import { AuthorizationCanGuard } from './authorization.can.guard'; const appRoutes: Routes = [ { path: '', component: HomeComponent }, { path: 'home', component: HomeComponent }, { path: 'forbidden', component: ForbiddenComponent }, { path: 'unauthorized', component: UnauthorizedComponent }, { path: 'securefiles', component: SecureFilesComponent, canActivate: [AuthorizationGuard], canLoad: [AuthorizationCanGuard] } ]; 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'; import { Subject } from 'rxjs'; import { OpenIdConfiguration, OpenIdInternalConfiguration } from '../models/auth.configuration'; import { AuthWellKnownEndpoints } from '../models/auth.well-known-endpoints'; import { PlatformProvider } from './platform.provider'; @Injectable({ providedIn: 'root' }) export class ConfigurationProvider { private DEFAULT_CONFIG: OpenIdInternalConfiguration = { stsServer: 'https://please_set', redirect_url: 'https://please_set', client_id: 'please_set', response_type: 'code', scope: 'openid email profile', hd_param: '', post_logout_redirect_uri: 'https://please_set', start_checksession: false, silent_renew: false, silent_renew_url: 'https://please_set', silent_renew_offset_in_seconds: 0, use_refresh_token: false, post_login_route: '/', forbidden_route: '/forbidden', unauthorized_route: '/unauthorized', auto_userinfo: true, auto_clean_state_after_authentication: true, trigger_authorization_result_event: false, log_console_warning_active: true, log_console_debug_active: false, iss_validation_off: false, history_cleanup_off: false, max_id_token_iat_offset_allowed_in_seconds: 3, isauthorizedrace_timeout_in_seconds: 5, disable_iat_offset_validation: false, storage: typeof Storage !== 'undefined' ? sessionStorage : null, }; private INITIAL_AUTHWELLKNOWN: AuthWellKnownEndpoints = { issuer: '', jwks_uri: '', authorization_endpoint: '', token_endpoint: '', userinfo_endpoint: '', end_session_endpoint: '', check_session_iframe: '', revocation_endpoint: '', introspection_endpoint: '', }; private mergedOpenIdConfiguration: OpenIdInternalConfiguration = this.DEFAULT_CONFIG; private authWellKnownEndpoints: AuthWellKnownEndpoints = this.INITIAL_AUTHWELLKNOWN; private onConfigurationChangeInternal = new Subject<OpenIdConfiguration>(); get openIDConfiguration(): OpenIdInternalConfiguration { return this.mergedOpenIdConfiguration; } get wellKnownEndpoints(): AuthWellKnownEndpoints { return this.authWellKnownEndpoints; } get onConfigurationChange() { return this.onConfigurationChangeInternal.asObservable(); } constructor(private platformProvider: PlatformProvider) {} setup(passedOpenIfConfiguration: OpenIdConfiguration, passedAuthWellKnownEndpoints: AuthWellKnownEndpoints) { this.mergedOpenIdConfiguration = { ...this.mergedOpenIdConfiguration, ...passedOpenIfConfiguration }; this.setSpecialCases(this.mergedOpenIdConfiguration); this.authWellKnownEndpoints = { ...passedAuthWellKnownEndpoints }; this.onConfigurationChangeInternal.next({ ...this.mergedOpenIdConfiguration }); } private setSpecialCases(currentConfig: OpenIdConfiguration) { if (!this.platformProvider.isBrowser) { currentConfig.start_checksession = false; currentConfig.silent_renew = false; currentConfig.use_refresh_token = false; } } }
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.
// Code Flow with PCKE or Implicit Flow authorize(urlHandler?: (url: string) => any) { if (this.configurationProvider.wellKnownEndpoints) { this.authWellKnownEndpointsLoaded = true; } if (!this.authWellKnownEndpointsLoaded) { this.loggerService.logError('Well known endpoints must be loaded before user can login!'); return; } if (!this.oidcSecurityValidation.config_validate_response_type(this.configurationProvider.openIDConfiguration.response_type)) { // invalid response_type return; } this.resetAuthorizationData(false); this.loggerService.logDebug('BEGIN Authorize Code Flow, no auth data'); let state = this.oidcSecurityCommon.authStateControl; if (!state) { state = Date.now() + '' + Math.random() + Math.random(); this.oidcSecurityCommon.authStateControl = state; } const nonce = 'N' + Math.random() + '' + Date.now(); this.oidcSecurityCommon.authNonce = nonce; this.loggerService.logDebug('AuthorizedController created. local state: ' + this.oidcSecurityCommon.authStateControl); let url = ''; // Code Flow if (this.configurationProvider.openIDConfiguration.response_type === 'code') { // code_challenge with "S256" const code_verifier = 'C' + Math.random() + '' + Date.now() + '' + Date.now() + Math.random(); const code_challenge = this.oidcSecurityValidation.generate_code_verifier(code_verifier); this.oidcSecurityCommon.code_verifier = code_verifier; if (this.configurationProvider.wellKnownEndpoints) { url = this.createAuthorizeUrl( true, code_challenge, this.configurationProvider.openIDConfiguration.redirect_url, nonce, state, this.configurationProvider.wellKnownEndpoints.authorization_endpoint || '' ); } else { this.loggerService.logError('authWellKnownEndpoints is undefined'); } } else { // Implicit Flow if (this.configurationProvider.wellKnownEndpoints) { url = this.createAuthorizeUrl( false, '', this.configurationProvider.openIDConfiguration.redirect_url, nonce, state, this.configurationProvider.wellKnownEndpoints.authorization_endpoint || '' ); } else { this.loggerService.logError('authWellKnownEndpoints is undefined'); } } if (urlHandler) { urlHandler(url); } else { this.redirectTo(url); } }
The user is redirected to the default IdentityServer4 login html view:
And then to the permissions page, which shows what the client is requesting.
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.
// Implicit Flow authorizedImplicitFlowCallback(hash?: string) { this._isModuleSetup .pipe( filter((isModuleSetup: boolean) => isModuleSetup), take(1) ) .subscribe(() => { this.authorizedImplicitFlowCallbackProcedure(hash); }); } // Implicit Flow private authorizedCallbackProcedure(result: any, isRenewProcess: boolean) { this.oidcSecurityCommon.authResult = result; if (!this.configurationProvider.openIDConfiguration.history_cleanup_off && !isRenewProcess) { // reset the history to remove the tokens window.history.replaceState({}, window.document.title, window.location.origin + window.location.pathname); } else { this.loggerService.logDebug('history clean up inactive'); } if (result.error) { if (isRenewProcess) { this.loggerService.logDebug(result); } else { this.loggerService.logWarning(result); } if ((result.error as string) === 'login_required') { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, ValidationResult.LoginRequired)); } else { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, ValidationResult.SecureTokenServerError)); } this.resetAuthorizationData(false); this.oidcSecurityCommon.authNonce = ''; if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.unauthorized_route]); } } else { this.loggerService.logDebug(result); this.loggerService.logDebug('authorizedCallback created, begin token validation'); this.getSigningKeys().subscribe( jwtKeys => { const validationResult = this.getValidatedStateResult(result, jwtKeys); if (validationResult.authResponseIsValid) { this.setAuthorizationData(validationResult.access_token, validationResult.id_token); this.oidcSecurityCommon.silentRenewRunning = ''; if (this.configurationProvider.openIDConfiguration.auto_userinfo) { this.getUserinfo(isRenewProcess, result, validationResult.id_token, validationResult.decoded_id_token).subscribe( response => { if (response) { this._onAuthorizationResult.next( new AuthorizationResult(AuthorizationState.authorized, validationResult.state) ); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.post_login_route]); } } else { this._onAuthorizationResult.next( new AuthorizationResult(AuthorizationState.unauthorized, validationResult.state) ); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.unauthorized_route]); } } }, err => { /* Something went wrong while getting signing key */ this.loggerService.logWarning('Failed to retreive user info with error: ' + JSON.stringify(err)); } ); } else { if (!isRenewProcess) { // userData is set to the id_token decoded, auto get user data set to false this.oidcSecurityUserService.setUserData(validationResult.decoded_id_token); this.setUserData(this.oidcSecurityUserService.getUserData()); } this.runTokenValidation(); this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.authorized, validationResult.state)); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.post_login_route]); } } } else { // something went wrong this.loggerService.logWarning('authorizedCallback, token(s) validation failed, resetting'); this.loggerService.logWarning(window.location.hash); this.resetAuthorizationData(false); this.oidcSecurityCommon.silentRenewRunning = ''; this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, validationResult.state)); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.unauthorized_route]); } } }, err => { /* Something went wrong while getting signing key */ this.loggerService.logWarning('Failed to retreive siging key with error: ' + JSON.stringify(err)); this.oidcSecurityCommon.silentRenewRunning = ''; } ); } }
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.
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.
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
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
You sir are one very “updated” gentlemen. Excellenet article
Thanks
Great article. Are there any changes in how the protocoll is implemented in IdentityServer4, or would this angular2 client implementation also work directly against IdentityServer3?
Thanks
The protocol is the same. It should work without problems using IdentityServer3 but I have not tested this.
Greetings Damien
Implicit flow is all fine and dandy until you need to show image resources that are secured.
Hi Dave
Thanks for your comment. There is no difference between an image resource and a json resource, both can be secured and the Implicit Flow has nothing to do with this. The resource server has to provide secured images. The client needs to add the token to the image HTTP request. If it is not possible to add it to the header on the client, it can be added to the query string and middleware can be added to the resource server to handle this, but this is not a problem of the Implicit flow.
What type of problem do/did you have with images and Implicit Flow?
Greetings Damien
Browsers don’t add the token to the header, and if you add it to the query string you’ll break caching.
Hi Dave
If the image is secured, you need to break the cache (Or use a header token) otherwise the next person to use your browser can view the image as well, without logging in.
Greetings Damien
Technically, you are correct, but I’m finding that a very weak argument.
True, not important if no users share there browsers, it’s a trade between requirements, you can also change the way the client gets the secured images.
Is it really secure to use implicit flow with a SPA ? Is it secure to store the Token in the browser’s storage ?
Is it really secure to use implicite flow ina SPA ? Is it secure to store the Token in the browser’s storage ?
No security is really secure. Storing the token in local storage has its CONs as with not saving the token client side at all, or saving it in a cookie. All have PROs and CONS.
Greetings Damien
I’m wondering about security, if it is that simple to get the entire source of everything with just one GET in a browser?
http://localhost:5000/app/dataeventrecords/dataeventrecords-create.component.ts
Tada… Login? Nada…
Hi Neil, thanks for your comment.
The client has always the src code in a SPA,the uglified bundle for production or the dev build ts like you pointed out. That’s why the OpenID Connect Implicit Flow is recommended for SPAs.
Greetings Damien
Thanks for the excellent example. A question please, where is the index.html or other templates file in AngularClient solution. It is very confusing to follow the code with this post.
Hi Aakash
Thanks
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/tree/master/src/Angular2Client/wwwroot
Greetings Damien
I wanted to add this link as this seems to be my problem I mentioned in the previous comment:
https://github.com/aspnet/DataProtection/issues/139
Any idea how to resolve this?
Hi
I have three Angular 2 projects:
1. client.mydomain.com
2. company.mydomain.com
3. admin.mydomain.com
All are subdomains of my domain.
I want to use identity server to allow single login for all this websites.
What is the best way to create such flow?
OpenID Connect Implicit Flow, one identityServer Client config per app, and then each IdentityServer user config has the claims for each app. Sign out should do a IdentityServer log out .Greetings Damien
Yes, I know this.
I mean that when user already login on client.mydomain.com he has token and when user goes to company.mydomain.com he doesn’t have token, so SPA had to go to identity server and get it.
The problem is that user already login and identity server just redirects user back. User doesn’t have to enter login and password again because he already logged in and this is GOOD! I just want to remove this unnecessary redirect.
Can I request token from Identity Server if user already logged in without redirects?
Thank you!
Hi you could implemented PreAuthenticate
https://github.com/IdentityServer/IdentityServer3.Samples/issues/61
Hope this is what your looking for.
Greetings Damien
Damien, thank you.
After reading different posts it looks like I just need to create custom solution using IdentityServer4.Core
I decided to create auth.mydomain.com
On this website I will have method that will issue token.
I’m not sure if I’m right, please provide your idea.
P.S.
When user from client.mydomain.com need to access info he will be redirected to auth.mydomain.com
Then back with token.
Then when user go to company.mydomain.com, Angular during loading will send GET request to auth.mydomain.com. (e.g. auth.mydomain.com/token) and because user already authorized app will get token in response.
Is that correct flow?
Hi Damien,
Can you share how the process of updating to webpack? It is not very clear from the code. How does the application determine the entry point when you use webpack.
Tayo
Hi Tayo, sounds like a good idea, need a couple of days though, a busy week coming up, don’t have much time.
Greetings Damien
I am using your demo and this is great – however, I am having some very simple Angular 2.0 issue that I can’t solve. I am not able to use this.router.navigate([‘Unauthorized’]); from SecurityServce.ts, however, it works very well from app.component. I was doing a lot of samples and always was able to navigate from other class than app.component. I am still using router-deprecated but it should not be a problem. Is it any way I can email you my very simple project to take a look? Thanks!
Hi Alex, Maybe your missing an import. Try updating to the new router as the old one is disappearing. I also had a bug with the old router that the IdentityServer redirect did not work for a angular route so I set the redirect to the parent. This bug in angular is closed as the router is no longer supported.
Greetings Damien
I figured out and updated to use of new router. However, redirect is still does not work when we get back with Denied for example. Very interesting – redirect works from services but not when we are returning back from identity server.
What I mean, that when it is returning from IdentityServer, route.Navigate never worked for me.
However, I have another question, could you recommend this approach for building real web site? Another approach is to use Identity Server at API level, only to return data when propertu authenticated. What do you think?
Hi Alex
This depends on your environment. I would always recommend OpenID Connect Implicit Flow for SPAs. The big choice is, if this should be a separate application (IdentityServer) or included in the server application. Your deployment, target clients define this. This depends on your system, targets.
Hope this helps
Greetings Damien
We are building SPA application with Angular 2.0 front end – do you mean that the approach in this article is enough and there no need to hook identity server at API level? Just your opinion 🙂
Hey man, thanks a lot for your efforts!
I’m wondering if there would be a lot of work to implement hybrid flow? Mainly I’m curious because it would make it great for using refresh tokens.
I have to put this app into different folder structure to debug using Chrome debugger.
great new version – thanks a lot!
Thanks
Another interesting question – when I updated ts file, SecurityService for example, build and run, I don’t see my changes. Actually, I am using the Chrome debugger and look at webpack://, wwwroot/app. I did not make any config changes, just got this sample. Any ideas? Thanks!
Hi Alex, you need to run the webpack build
greetings Damien
Very interesting – I am working with latest version – and redirect after returning back from Identity Server is still not working for me:
if (authResponseIsValid) {
this.SetAuthorizationData(token, id_token);
console.log(this.retrieve(“authorizationData”));
// router navigate to DataEventRecordsList
this._router.navigate([‘/dataeventrecords’]);
}
else {
this.ResetAuthorizationData();
this._router.navigate([‘/Unauthorized’]);
}
When authResponseIsValid, it updates menu system to show Logout but does not display data event records. When authResponseIsValid is not valid, it does not display anything just still has Login at menu. Could you check please if your local version works correctly? I was having this issue some time ago with depreciated router, however, it is still there! Thanks!
This is what I see in Chrome debugger: Uncaught (in promise): Error: Cannot match any routes: ”
Thanks, I need to fix this, I’ll create a github issue, so I don’t forget
I also not able to change TS code and see changes in Chrome debugger – it is still same code as in original article. May be it is something with Webpack configuration?
I am able to compile code, using Task Runner Explore! This is what I see there – if I put this._router.navigate([‘/Unauthorized’]); in Authorize() it works, if I pit into AuthorizedCallback() – it does not work. Very interesting!
Adding redirectTo sections solves this issue for me – not sure if it is the best approach:
export const routes: RouterConfig = [
{ path: ”, component: HomeComponent, pathMatch: ‘full’ },
{ path: ‘Forbidden’, component: ForbiddenComponent, terminal: true },
{ path: ‘Unauthorized’, component: UnauthorizedComponent, terminal: true },
{ path: ‘securefile/securefiles’, component: SecureFilesComponent, terminal: true },
{ path: “**”, redirectTo: ‘dataeventrecords’ },
cool, thanks
Do you know by any chance that we can use Windows Authentication with Identity Server 4 in same way as it was possible with 3 version?
Very interesting question – your TS files are under wwwroot? What would you recommend – under wwwroot or beyond? Thanks!
The TS files in wwwroot are not really a good idea. Here’s a better solution for a angular 2 project
https://github.com/damienbod/Angular2WebpackVisualStudio
Microsoft are also working on a aspnetcore angular2 template which is nice.
The angular-cli project is also good.
The main or big challenge is to have a proper production build, but also possible to debug in dev.
greetings Damien
Thanks, Demien. Is not enough to put just bundle files into production and debug minified version there?
Hi Alex
Yes, but everything inside the wwwroot is published per default, so the question is does it matter if you deliver your src files.
If the app src, debug files were outside the wwwroot and could be debugged but only the build files either dev or prod were inside the wwwroot, this would feel better for me.
And at present most of the aspnetcore, angular2 templates cannot be used in production…
Webpack solutions or angular-cli are the ones which will work.
Greetings Damien
What is the best way to get Token in ResourceServer (Web API) and get user name of the logged user?
Never thought about this. The user name is sent with every request in the access_token. You could use this to get the user name (for example a filter) or send it as payload in the request.
Greetings Damien
Very interesting – when I load URL from browser, it actually does not work. Like in my app, the following url from RouteLink, will open correct page: https://localhost:44312/request/list/136.
However, if I reload, it does not work correctly.
I am trying to put into Startup.cs code like that:
var angularRoutes = new[] {
“/home”,
“/request”,
“/request/details”,
“/request/list”
};
It still does not work correctly. I think I can create MVC controller but I don’t want to do something like that. I also reading on Web – this is what they are telling:
That’s a browser feature.
Angular by default uses HTML5 pushstate (PathLocationStrategy in Angular slang).
You either need a server that processes all requests like it were requesting index.html or you switch to HashLocationStrategy (with # in the URL for routes)
What is the best approach according with your experience?
I figured out this issue – however, I am not sure that this is the best approach. I put to the index.html, added additional path, request, to Startup.css: var angularRoutes = new[] { “/home”, “/request” }; plus some more small changes and I can reload page or just use url to activate corresponding state. Thanks again for all those samples and help!
Hello,
what is the workflow to work with Angular2Client project? (With angular-client is easier because ng serve do all it for you. What is the right approach with this type of project?)
I’ve added AuthManager.ts in Angular2Client project. The path for this service is: wwwroot\app\services\AuthManager.ts
The problem is that this added file is not autogenerating the .js file file from the ts file.
Also I’ve changed the app.module.ts but when I open it in the browser the code is not the code I have written.
How could I agilize this autogeneration/updating proccess? it would be great some webpack task or something like this.
Ok,
I have installed webpack task runner explorer addin,
changed tsconfig.json:
“compileOnSave”: true,
“buildOnSave”: true
And runned webpack task: Watch – Development
Now I’m able to code faster!
I did all of that to code faster, but when I changed index.html for example, it does not change it at index.html under wwwrooot.
This is what I have – at the top of webpack.config.js:
///
in tsconfig.json:
{
“compilerOptions”: {
“target”: “es5”,
“module”: “commonjs”,
“moduleResolution”: “node”,
“removeComments”: true,
“emitDecoratorMetadata”: true,
“experimentalDecorators”: true,
“noEmitHelpers”: false,
“sourceMap”: true
},
“exclude”: [
“node_modules”
],
“compileOnSave”: true,
“buildOnSave”: true
}
Am I missing something? Thanks!
cool, I will change to default to this. thanks
Greetings Damien
What is the best way to make a registration process and user persistence ? Asp core identity or identity server have a user persistence store as configuration persistence ?
Hi Janick I’ve just updated the git repo with an example for this. It uses Identity with SQLite and EFCore with IdentityServer4. I think this is a good solution with maybe a different database.
Greetings Damien
Fantastic post Damien – Thanks for the hard work. It’s got me motivated to look at .NET Core which i’ve been putting off for a while!
One quick question – I can’t seem to get access to the username of the user. I can see someone asked this question earlier – but i’m unsure on your response.
I’ve tried adding additional Allowed Scopes to the Client for “Email” and “profile”. But the username is never on the Claims Principal when i send down the token to the API – any ideas why these claims may be getting missed out on the token?
Cheers,
Richard
Fantastic post Damien – Thanks for the hard work. It’s got me motivated to look at .NET Core which i’ve been putting off for a while!
One quick question – I can’t seem to get access to the username of the user. I can see someone asked this question earlier – but i’m unsure on your response.
I’ve tried adding additional Allowed Scopes to the Client for “Email” and “profile”. But the username is never on the Claims Principal when i send down the token to the API – any ideas why these claims may be getting missed out on the token?
Cheers,
Richard
Hi Richard, thanks for your feedback.
You could add your claims in an IProfileService implementation and these will be available for the client.
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/blob/master/src/IdentityServerWithIdentitySQLite/IdentityWithAdditionalClaimsProfileService.cs
Perfect ! Works a treat! Thanks!
Hi Damien,
Many thanks for a brilliant and it looks like the only useful post on the Internet for this topic.
However for some reason I cannot launch Angular2Client and IdentityServerWithAspNetIdentitySqlite projects from VS2015. The problem is that both do not launch IIS Express while starting in debug mode. And at the same time the ResourceServer project can be launched normally: it starts IIS Express and browser and works fine.
Any ideas how to fix this please?
Regards,
Sergey
Hi Sergey, thanks. Both projects should run. 2 possible problems, the IIS does not start using the configured HTTPS port, this is configured in the project settings and also the client code configuration. Also, maybe the webpack build needs to be started for the Angular 2 project.
Greetings Damien
Damien thanks for the good article. I am looking at a few things and I think you might also have things to say on this…
1) using /.well-known/jwks to get the list of end points and configure the client.
2) making a connection to the Routing system with the Guard hooks to make routing and a users roles work together.
I am looking at your other blog posts right now as it does look like you may have already written something…
Hi figuerres, thanks, I planning to look into the routing with guards etc.
Greetings Damien
[…] am following the example provided by damienbod Ang2 Id Serv 4 OIDC, where I have the following : an identity Server (modified implementation), Resource API and ng-2 […]
I’m not able to make the migration of databse. Do you have an idea to resolve this problem ?
Which database, project. Delete the Migrations, change the EF core providers to your DB and add
dotnet ef migrations add yourName
dotnet ef update database
The connectionString must match your db,
Greetings Damien
This is awesome, is there an easy way to add social logins to this example?
Yes, exactly the same as the examples from IdentityServer4, shouldjust require a change in the startup and then the razor view
Thanks and greetings Damien
Hi This is a great article, Can you please let me know if there is any sample with angular2 with out asp.net Identity (As we are planning to have our own identity/user management system with dapper implementation)
Worth mentioning that this exists if you are following along with the Identity quickstart tutorial and want to use the client they reference, or if you are using Angular CLI (which this sample makes use of):
https://github.com/jmurphzyo/Angular2OidcClient
thanks
Hi,
Thank you for these great articles. Running into issue when I am trying to run the sample with Angular2 Client. The page keeps showing “This is server routing, not angular2 routing”. It seems like the Angular app is not loading at all. Really appreciate your efforts.
Thanks
Hi Kay
I’ll check this, will get back to you, the client routes should be executed first, then the server. If the requested route is not defined in the startup.cs, the server routing is used.
I will remove the MVC routing completely for this project and use just static files.
Greetings Damien
Hi Kay
I do’t have this problem, just checked. Can you send me more info?, maybe a github issue
Greetings Damien
Hi Damien,
Thank you for following up on this. I am not sure either why this is not running on my machine. I am trying to run application directly from your repo at this point and still facing the same issue. Makes me think I am doing something wrong on how to run the application may be ?
I have set IdentityServerWithAspNetIdentitySqlite & Anguar2client as startup projects and running the solution from VS 2015.
The IdentityServer launches just fine but the Angular2Client at following URL
https://localhost:44311/ shows “This is server routing, not angular2 routing” message.
What am I missing here ?
Thanks,
Kay
Hi Kay you might need to build the angular app
$ npm install
$ npm run build-production
You also need to start the 2 resource servers
Great, it’s working now. The only thing I was missing was npm run build-production. Thanks for all your help and these great samples.
There another question I had regarding Identityserver, unrelated to this specific post. I was wondering if I could reach out to you separately over an email ?
Thanks
cool
sent
cheers Damien
Great, thanks. Sent you an email with the details.
Thank you so much for your great articles!
I am trying to implement this (IdentityServer4 with .net core Identity and Angular 2 app) and am running into an issue with redirection back to the client once they login. My angular 2 app will go to the connect/authorize endpoint of identity server then redirect to the login page and have something like this:
http://localhost:5000/account/login?returnUrl=%2Fconnect%2Fauthorize%2Flogin%3Fresponse_type%3Did_token%2520token%26client_id%3Dangular2%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A3000%252Fsecure%26scope%3Dapiprovider%2520profile%2520openid%26nonce%3DN0.70919710628708831486571833014%26state%3D14865718330140.6582151509561325
The user enters their credentials and clicks on login, then the AccountController.Login functionality kicks off but the returnURL information is obviously not right because it contains the entire set of parameters sent up from the Angular 2 app (I copied what you had in the Angular2App SecurityService file to make the request so that makes sense that it would). If using identity server with identity, do I manually parse this query string to get the actual return URL or is there another method that I am missing in my pipeline that handles this automatically. I get nervous about the idea of parsing this manually, not because I cant do it, but because if it ever changes, it would break everything.
It just hit me that I did not understand the flow of this process correctly. I think I had the order wrong. The user logs in via the AccountController.Login method, then is redirected to the connect/authorize endpoint defined in the query string that I pasted above. The user is granted a id token by the identity server and then sent to the consent path if that is required. My issue was that I didnt explicitly set Consent being false, so it was forwarding to consent and failing there. Once I changed the configuration of the angular2 client to not require consent, it redirected back to the angular 2 app correctly.
So I think I answered my own question. Now to see if I can authorize against an resource endpoint and add a client that DOES require consent!
great!
Hi Damien,
Thanks for the article and github repo.
I’m fairly new to OpenId Connect and I’ve been trying to understand the implicit flow by reading the specs and looking at your examples.
One thing I have noticed is that the Angular example does not validate the id token, apart from checking the nonce.
Looking at the specs, they say “Clients MUST validate the ID Token per Section 2.2.1.”.
If the nonce from the id token is being used, then should the client validate the signature of the id token too?
Thanks
Terry
Hi terry, I thought I validated, will fix this, thanks
hi terry, I’ve implemented this now
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/tree/master/src/ResourceWithIdentityServerWithClient/angularApp/app/auth
greetings Damien
where is the OidcSecurityService?
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/tree/master/src/AngularClient/angularApp/app/auth
Thanks a lot
Very Helpfull
Hey Damien.
First of all, great posts! Thank you very much for all the useful knowledge you shared.
Keep up the good work!
I’m having problems when trying to access protected files from client (angular client).
Actually problem is that API – ResourceFileServer (44378 port) can’t hit Identity server – IdentityServerWithIdentitySQLite (44318 port )
I can access the discovery address from browser – https://localhost:44318/.well-known/openid-configuration, but it seems API can’t access it for some reason. Any ideas please?
No Errors in logs for Identity Server app.
Log from Web API:
An unhandled exception was thrown by the application. (8c14976b)
System.InvalidOperationException: Discovery endpoint https://localhost:44318/.well-known/openid-configuration is unavailable: System.Net.Http.HttpRequestException: An error occurred while sending the request. —> System.Net.Http.WinHttpException: A security error occurred
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Net.Http.WinHttpHandler.d__105.MoveNext()
— End of inner exception stack trace —
Thanks
Seems like the port is not configured. Try resetting this in the debug properties page, if you change this, you will have to re-configure the client etc. Greetings Damien
Hey Damien,
Thanks for your reply!!
Just a notice, I can’t access any of the sites without confirming security exception about SSL.
Could that be an issue?
I did import my certificate into local machine -> trusted root certification authorities
Thanks!
Also, I tried changing port in debug settings, but that didn’t help so I’m out of ideas.
Hey there.
Even after moving IdentityServer to IIS site i still get the same error:
https://www.angularcoreidentityserver.com/.well-known/openid-configuration is unavailable: System.Net.Http.HttpRequestException: An error occurred while sending the request. —> System.Net.Http.WinHttpException: A security error occurred
Do you see anything more in the logs?
Nothing else in logs,
except: Even after moving IdentityServer to IIS site i still get the same error:
https://www.angularcoreidentityserver.com/.well-known/openid-configuration is unavailable: System.Net.Http.HttpRequestException: An error occurred while sending the request. —> System.Net.Http.WinHttpException: A security error occurred
Any ideas?
possibly CORS issue as this is two different ports ?? site a on port 123 not connecting to site b on port 456 ??
Not working. Error:
An unhandled exception was thrown by the application. (8c14976b)
System.InvalidOperationException: Discovery endpoint https://localhost:44318/.well-known/openid-configuration is unavailable: System.Net.Http.HttpRequestException:
Is the IdentityServer4 running at this port and https?
Yeah, same for me, and it’s running at the port and https.
At Ani, Jamie
I’m trying to reproduce this and I can’t. I created a new issue for this.
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/issues/54
Could you try the following
1) Rebuild the whole solution
2) Stop all scripts running in the task runner
2) delete everything in .\src\AngularClient\node_modules
3) using the commandline at .\src\AngularClient
> npm install
> npm run build-production
4) start IdentityServerWithIdentitySQLite
5) start ResourceServer
6) start ResourceFileServer
7) start AngularClient
Greetings Damien
Can’t find ngc when i try to run build-production task. I think you should install cli as dev-dependency.
if I try to create a new API from scratch get data from .well-known URL – “https://localhost:44318/.well-known/openid-configuration”, I still get the error – kinda same error as ResourceFileServer API produces.
However, If I use it like this (with ServerCertificateCustomValidationCallback returning true):
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
return true;
};
using (var client = new HttpClient(httpClientHandler))
{
try
{
var response = await client.GetAsync(“https://localhost:44318/.well-known/openid-configuration”);
return response;
}
catch (HttpRequestException ex)
{
var c = ex;
throw;
}
}
}
That works. Any ideas?
when i run the wepack task runner and run the Production mode its give me following errors but its working fine with Development:
ERROR in ./angular2App/main-aot.ts
Module not found: Error: Can’t resolve ‘../aot/angular2App/app/app.module.ngfactory’ in ‘D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client\angular2App’
@ ./angular2App/main-aot.ts 5:0-81
ERROR in [default] D:\Sprints\Partner View\Sprint-3.0.7\PV\dev-latest\partnerview-v3\PartnerView Angular2\PartnerView.UI\src\Angular2Client\angular2App\main-aot.ts:7:35
Cannot find module ‘../aot/angular2App/app/app.module.ngfactory’.
seems like the path is incorrect. Check the aot folder and see if it matches the paths defined in the webpack.prod file and the tsconfig-aot.json file.
there is a line ‘import { AppModuleNgFactory } from ‘../aot/angular2App/app/app.module.ngfactory’;’ in ‘main-aot.ts’ file but in my solution there is not aot folder exist and its giving the error due to this aot folder. Like you can see we have following hierarchy “src/AngularClient/angularApp/app/” so how this aot folder will create.
Kindly help me i am stuck on this its working fine with dev but when i use Run- Production from webpack task runner its giving the upper error
If the path is incorrect, you need to change this in the webpack.prod.js file and also in the tsconfig-aot file
Greetings Damien
Its giving problem in main-aot.ts files at “import { AppModuleNgFactory } from ‘../aot/src/app/app.module.ngfactory'” this line because “app.module.ngfactory” not exist at that location. I also checked in your this application and i found that you also don’t have “aot” folder and there is no “app.module.ngfactory” file too.
I have checked somewhere that “app.module.ngfactory” file not physically exist but we also don’t have “aot” folder and you have given the “aot” in the ‘../aot/src/app/app.module.ngfactory'” in main-aot.ts.
Can you please elaborate this.I shall be really thankful to you.Kindly help me out. 😦
Hi Manjit, seems like the aot was not built. Try from the cmd to test:
> npm install
> npm run build-production
Greetings Damien
In my package.json
“scripts”: {
“ngc”: “ngc -p ./tsconfig-aot.json”,
“start”: “webpack-dev-server –inline –progress –port 8080”,
“webpack-dev”: “set NODE_ENV=development&& webpack”,
“webpack-prod”: “set NODE_ENV=production&& webpack”,
“build”: “npm run webpack-dev”,
“buildProduction”: “npm run ngc && npm run webpack-prod”
},
So i run
> npm install
> npm run buildProduction
But its giving me 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 ]
And second 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 ]