From f7e2a3d7603d66bc9a1100f0426e0c1b1579d05b Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 1 Aug 2017 06:00:08 -0500 Subject: [PATCH] Change Angular auth oidc clients (#10) --- ClientApp/ClientApp/app/app.module.client.ts | 6 +- ClientApp/ClientApp/app/app.module.server.ts | 6 +- ClientApp/ClientApp/app/app.module.shared.ts | 11 +- .../components/callback/callback.component.ts | 13 -- .../fetchdata/fetchdata.component.ts | 4 +- .../components/navmenu/navmenu.component.html | 24 +- .../components/navmenu/navmenu.component.ts | 63 +++-- .../components/services/auth-guard.service.ts | 20 -- .../app/components/services/auth.service.ts | 220 +++++++----------- .../services/global.events.manager.ts | 16 -- .../unauthorized/unauthorized.component.ts | 5 +- ClientApp/package-lock.json | 14 +- ClientApp/package.json | 5 +- 13 files changed, 154 insertions(+), 253 deletions(-) delete mode 100644 ClientApp/ClientApp/app/components/callback/callback.component.ts delete mode 100644 ClientApp/ClientApp/app/components/services/auth-guard.service.ts delete mode 100644 ClientApp/ClientApp/app/components/services/global.events.manager.ts diff --git a/ClientApp/ClientApp/app/app.module.client.ts b/ClientApp/ClientApp/app/app.module.client.ts index be23bb0..90ec4c0 100644 --- a/ClientApp/ClientApp/app/app.module.client.ts +++ b/ClientApp/ClientApp/app/app.module.client.ts @@ -4,10 +4,6 @@ import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { sharedConfig } from './app.module.shared'; -import { AuthService } from './components/services/auth.service'; -import { GlobalEventsManager } from './components/services/global.events.manager'; -import { AuthGuardService } from './components/services/auth-guard.service'; - @NgModule({ bootstrap: sharedConfig.bootstrap, declarations: sharedConfig.declarations, @@ -20,7 +16,7 @@ import { AuthGuardService } from './components/services/auth-guard.service'; providers: [ { provide: 'ORIGIN_URL', useValue: location.origin }, { provide: 'API_URL', useValue: "http://localhost:5001/api/" }, - AuthService, AuthGuardService, GlobalEventsManager + ...sharedConfig.providers ] }) export class AppModule { diff --git a/ClientApp/ClientApp/app/app.module.server.ts b/ClientApp/ClientApp/app/app.module.server.ts index b532b5f..6468367 100644 --- a/ClientApp/ClientApp/app/app.module.server.ts +++ b/ClientApp/ClientApp/app/app.module.server.ts @@ -2,10 +2,6 @@ import { NgModule } from '@angular/core'; import { ServerModule } from '@angular/platform-server'; import { sharedConfig } from './app.module.shared'; -import { AuthService } from './components/services/auth.service'; -import { GlobalEventsManager } from './components/services/global.events.manager'; -import { AuthGuardService } from './components/services/auth-guard.service'; - @NgModule({ bootstrap: sharedConfig.bootstrap, declarations: sharedConfig.declarations, @@ -14,7 +10,7 @@ import { AuthGuardService } from './components/services/auth-guard.service'; ...sharedConfig.imports ], providers: [ - AuthService, AuthGuardService, GlobalEventsManager + ...sharedConfig.providers ] }) export class AppModule { diff --git a/ClientApp/ClientApp/app/app.module.shared.ts b/ClientApp/ClientApp/app/app.module.shared.ts index f11327f..145efa2 100644 --- a/ClientApp/ClientApp/app/app.module.shared.ts +++ b/ClientApp/ClientApp/app/app.module.shared.ts @@ -6,12 +6,10 @@ import { NavMenuComponent } from './components/navmenu/navmenu.component'; import { HomeComponent } from './components/home/home.component'; import { FetchDataComponent } from './components/fetchdata/fetchdata.component'; import { CounterComponent } from './components/counter/counter.component'; -import { CallbackComponent } from './components/callback/callback.component'; import { UnauthorizedComponent } from './components/unauthorized/unauthorized.component'; +import { AuthModule } from 'angular-auth-oidc-client'; import { AuthService } from './components/services/auth.service'; -import { GlobalEventsManager } from './components/services/global.events.manager'; -import { AuthGuardService } from './components/services/auth-guard.service'; export const sharedConfig: NgModule = { bootstrap: [ AppComponent ], @@ -21,19 +19,18 @@ export const sharedConfig: NgModule = { CounterComponent, FetchDataComponent, HomeComponent, - CallbackComponent, UnauthorizedComponent ], imports: [ + AuthModule.forRoot(), RouterModule.forRoot([ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, - { path: 'callback', component: CallbackComponent }, { path: 'unauthorized', component: UnauthorizedComponent }, { path: 'counter', component: CounterComponent }, - { path: 'fetch-data', component: FetchDataComponent, canActivate:[AuthGuardService] }, + { path: 'fetch-data', component: FetchDataComponent }, { path: '**', redirectTo: 'home' } ]) ], - providers: [ AuthService, AuthGuardService, GlobalEventsManager ] + providers: [ AuthService ] }; diff --git a/ClientApp/ClientApp/app/components/callback/callback.component.ts b/ClientApp/ClientApp/app/components/callback/callback.component.ts deleted file mode 100644 index 779aad3..0000000 --- a/ClientApp/ClientApp/app/components/callback/callback.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component } from '@angular/core'; -import { AuthService } from '../services/auth.service' - -@Component({ - selector: 'callback', - template: '' -}) - -export class CallbackComponent { - constructor (private _authService: AuthService){ - _authService.endSigninMainWindow(); - } -} \ No newline at end of file diff --git a/ClientApp/ClientApp/app/components/fetchdata/fetchdata.component.ts b/ClientApp/ClientApp/app/components/fetchdata/fetchdata.component.ts index d302f88..6e85727 100644 --- a/ClientApp/ClientApp/app/components/fetchdata/fetchdata.component.ts +++ b/ClientApp/ClientApp/app/components/fetchdata/fetchdata.component.ts @@ -9,8 +9,8 @@ import { AuthService } from '../services/auth.service'; export class FetchDataComponent { public forecasts: WeatherForecast[]; - constructor(http: Http, @Inject('API_URL') apiUrl: string, authService: AuthService) { - authService.AuthGet(apiUrl + 'SampleData/WeatherForecasts').subscribe(result => { + constructor(authService: AuthService, @Inject('API_URL') apiUrl: string) { + authService.get(apiUrl + 'SampleData/WeatherForecasts').subscribe(result => { this.forecasts = result.json() as WeatherForecast[]; }); } diff --git a/ClientApp/ClientApp/app/components/navmenu/navmenu.component.html b/ClientApp/ClientApp/app/components/navmenu/navmenu.component.html index 2fad08f..ec53cb8 100644 --- a/ClientApp/ClientApp/app/components/navmenu/navmenu.component.html +++ b/ClientApp/ClientApp/app/components/navmenu/navmenu.component.html @@ -13,29 +13,19 @@ diff --git a/ClientApp/ClientApp/app/components/navmenu/navmenu.component.ts b/ClientApp/ClientApp/app/components/navmenu/navmenu.component.ts index b792c03..863afb3 100644 --- a/ClientApp/ClientApp/app/components/navmenu/navmenu.component.ts +++ b/ClientApp/ClientApp/app/components/navmenu/navmenu.component.ts @@ -1,32 +1,45 @@ -import { Component } from '@angular/core'; -import { AuthService } from '../services/auth.service' -import { GlobalEventsManager } from '../services/global.events.manager' +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; + +import { AuthService } from '../services/auth.service'; + @Component({ selector: 'nav-menu', templateUrl: './navmenu.component.html', styleUrls: ['./navmenu.component.css'] }) -export class NavMenuComponent { - public _loggedIn: boolean = false; - - constructor ( - private _authService: AuthService, - private _globalEventsManager: GlobalEventsManager) { - _globalEventsManager.showNavBarEmitter.subscribe((mode)=>{ - // mode will be null the first time it is created, so you need to igonore it when null - if (mode !== null) { - console.log("Global Event, sent: " + mode); - this._loggedIn = mode; - } - }); - } - - public login(){ - this._authService.startSigninMainWindow(); - } - - public logout(){ - this._authService.startSignoutMainWindow(); - } +export class NavMenuComponent implements OnInit, OnDestroy { + isAuthorizedSubscription: Subscription; + isAuthorized: boolean; + + constructor(public authService: AuthService) { + } + + ngOnInit() { + this.isAuthorizedSubscription = this.authService.getIsAuthorized().subscribe( + (isAuthorized: boolean) => { + this.isAuthorized = isAuthorized; + }); + + if (window.location.hash) { + this.authService.authorizedCallback(); + } + } + + ngOnDestroy(): void { + this.isAuthorizedSubscription.unsubscribe(); + } + + public login() { + this.authService.login(); + } + + public refreshSession() { + this.authService.refreshSession(); + } + + public logout() { + this.authService.logout(); + } } \ No newline at end of file diff --git a/ClientApp/ClientApp/app/components/services/auth-guard.service.ts b/ClientApp/ClientApp/app/components/services/auth-guard.service.ts deleted file mode 100644 index ab165e1..0000000 --- a/ClientApp/ClientApp/app/components/services/auth-guard.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable, Component } from '@angular/core'; -import { CanActivate, Router } from '@angular/router'; - -import { AuthService } from './auth.service'; - -@Injectable() -export class AuthGuardService implements CanActivate { - - constructor(private authService: AuthService, private router: Router) { - } - - canActivate() { - if (this.authService.loggedIn) { - return true; - } - else { - this.router.navigate(['unauthorized']); - } - } -} \ No newline at end of file diff --git a/ClientApp/ClientApp/app/components/services/auth.service.ts b/ClientApp/ClientApp/app/components/services/auth.service.ts index 7ded7b6..435352a 100644 --- a/ClientApp/ClientApp/app/components/services/auth.service.ts +++ b/ClientApp/ClientApp/app/components/services/auth.service.ts @@ -1,134 +1,83 @@ -import { Injectable, EventEmitter, Component } from '@angular/core'; +import { Injectable, Component, OnInit, OnDestroy } from '@angular/core'; import { Http, Headers, RequestOptions, Response } from '@angular/http'; import { Observable } from 'rxjs/Rx'; -import { Router } from "@angular/router"; -import { UserManager, Log, MetadataService, User } from 'oidc-client'; -import { GlobalEventsManager } from './global.events.manager'; - -const settings: any = { - authority: 'http://localhost:5000', - client_id: 'ng', - redirect_uri: 'http://localhost:5002/callback', - post_logout_redirect_uri: 'http://localhost:5002/home', - response_type: 'id_token token', - scope: 'openid profile apiApp', - - silent_redirect_uri: 'http://localhost:5002/silent-renew.html', - automaticSilentRenew: true, - accessTokenExpiringNotificationTime: 4, - // silentRequestTimeout:10000, - - filterProtocolClaims: true, - loadUserInfo: true -}; +import { Subscription } from 'rxjs/Subscription'; + +import { OidcSecurityService, OpenIDImplicitFlowConfiguration } from 'angular-auth-oidc-client'; @Injectable() -export class AuthService { - loggedIn = false; - mgr: UserManager; - userLoadedEvent = new EventEmitter(); - currentUser: User; - authHeaders: Headers; - - constructor( - private http: Http, - private router: Router, - private globalEventsManager: GlobalEventsManager) { - if (typeof window !== 'undefined') { - //instance needs to be created within the if clause - //otherwise you'll get a sessionStorage not defined error. - this.mgr = new UserManager(settings); - this.mgr - .getUser() - .then((user) => { - if (user) { - this.currentUser = user; - this.userLoadedEvent.emit(user); - } - }) - .catch((err) => { - console.log(err); - }); - this.mgr.events.addUserUnloaded((e) => { - //if (!environment.production) { - console.log("user unloaded"); - //} +export class AuthService implements OnInit, OnDestroy { + isAuthorizedSubscription: Subscription; + isAuthorized: boolean; + + constructor(public oidcSecurityService: OidcSecurityService, + private http: Http) { + + const openIDImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration(); + openIDImplicitFlowConfiguration.stsServer = 'http://localhost:5000'; + + openIDImplicitFlowConfiguration.redirect_url = 'http://localhost:5002/callback'; + // The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience. + // The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client. + openIDImplicitFlowConfiguration.client_id = 'ng'; + openIDImplicitFlowConfiguration.response_type = 'id_token token'; + openIDImplicitFlowConfiguration.scope = 'openid profile apiApp'; + openIDImplicitFlowConfiguration.post_logout_redirect_uri = 'http://localhost:5002/home'; + openIDImplicitFlowConfiguration.start_checksession = true; + openIDImplicitFlowConfiguration.silent_renew = true; + openIDImplicitFlowConfiguration.startup_route = '/home'; + // HTTP 403 + openIDImplicitFlowConfiguration.forbidden_route = '/forbidden'; + // HTTP 401 + openIDImplicitFlowConfiguration.unauthorized_route = '/unauthorized'; + openIDImplicitFlowConfiguration.log_console_warning_active = true; + openIDImplicitFlowConfiguration.log_console_debug_active = false; + // id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time, + // limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific. + openIDImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = 10; + + this.oidcSecurityService.setupModule(openIDImplicitFlowConfiguration); + } + + ngOnInit() { + this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe( + (isAuthorized: boolean) => { + this.isAuthorized = isAuthorized; }); + + if (window.location.hash) { + this.oidcSecurityService.authorizedCallback(); } } - clearState() { - this.mgr.clearStaleState().then(() => { - console.log('clearStateState success'); - }).catch(e => { - console.log('clearStateState error', e.message); - }); + ngOnDestroy(): void { + this.isAuthorizedSubscription.unsubscribe(); } - getUser() { - this.mgr.getUser().then((user) => { - console.log("got user"); - this.userLoadedEvent.emit(user); - }).catch(err => { - console.log(err); - }); + authorizedCallback() { + this.oidcSecurityService.authorizedCallback(); } - removeUser() { - this.mgr.removeUser().then(() => { - this.userLoadedEvent.emit(null); - console.log("user removed"); - }).catch(err => { - console.log(err); - }); + getIsAuthorized(): Observable { + return this.oidcSecurityService.getIsAuthorized(); } - startSigninMainWindow() { - this.mgr.signinRedirect({ data: 'some data' }).then(() => { - console.log("signinRedirect done"); - }).catch(err => { - console.log(err); - }); + login() { + console.log('start login'); + this.oidcSecurityService.authorize(); } - endSigninMainWindow() { - if (typeof window !== 'undefined') { - this.mgr.signinRedirectCallback().then((user) => { - console.log("signed in"); - this.loggedIn = true; - this.globalEventsManager.showNavBar(this.loggedIn); - this.router.navigate(['home']); - }).catch(err => { - console.log(err); - }); - } + refreshSession() { + console.log('start refreshSession'); + this.oidcSecurityService.authorize(); } - startSignoutMainWindow() { - this.mgr.signoutRedirect().then(resp => { - console.log("signed out", resp); - setTimeout(5000, () => { - console.log("testing to see if fired..."); + logout() { + console.log('start logoff'); + this.oidcSecurityService.logoff(); + } - }); - }).catch(err => { - console.log(err); - }); - }; - - endSignoutMainWindow() { - this.mgr.signoutRedirectCallback().then(resp => { - console.log("signed out", resp); - }).catch(err => { - console.log(err); - }); - }; - - /** - * Example of how you can make auth request using angulars http methods. - * @param options if options are not supplied the default content type is application/json - */ - AuthGet(url: string, options?: RequestOptions): Observable { + get(url: string, options?: RequestOptions): Observable { if (options) { options = this.setRequestOptions(options); } @@ -138,10 +87,7 @@ export class AuthService { return this.http.get(url, options); } - /** - * @param options if options are not supplied the default content type is application/json - */ - AuthPut(url: string, data: any, options?: RequestOptions): Observable { + put(url: string, data: any, options?: RequestOptions): Observable { let body = JSON.stringify(data); if (options) { @@ -153,11 +99,7 @@ export class AuthService { return this.http.put(url, body, options); } - /** - * @param options if options are not supplied the default content type is application/json - */ - - AuthDelete(url: string, options?: RequestOptions): Observable { + delete(url: string, options?: RequestOptions): Observable { if (options) { options = this.setRequestOptions(options); } @@ -167,10 +109,7 @@ export class AuthService { return this.http.delete(url, options); } - /** - * @param options if options are not supplied the default content type is application/json - */ - AuthPost(url: string, data: any, options?: RequestOptions): Observable { + post(url: string, data: any, options?: RequestOptions): Observable { let body = JSON.stringify(data); if (options) { @@ -182,22 +121,31 @@ export class AuthService { return this.http.post(url, body, options); } - - private setAuthHeaders(user: User) { - this.authHeaders = new Headers(); - this.authHeaders.append('Authorization', user.token_type + " " + user.access_token); - this.authHeaders.append('Content-Type', 'application/json'); - } - private setRequestOptions(options?: RequestOptions) { if (options) { - options.headers.append(this.authHeaders.keys[0], this.authHeaders.values[0]); + this.appendAuthHeader(options.headers); } else { - //setting default authentication headers - this.setAuthHeaders(this.currentUser); - options = new RequestOptions({ headers: this.authHeaders, body: "" }); + options = new RequestOptions({ headers: this.getHeaders(), body: "" }); } return options; } + + private getHeaders() { + let headers = new Headers(); + headers.append('Content-Type', 'application/json'); + this.appendAuthHeader(headers); + return headers; + } + + private appendAuthHeader(headers: Headers) { + const token = this.oidcSecurityService.getToken(); + + if (token == '') return; + + const tokenValue = 'Bearer ' + token; + headers.append('Authorization', tokenValue); + } + + } \ No newline at end of file diff --git a/ClientApp/ClientApp/app/components/services/global.events.manager.ts b/ClientApp/ClientApp/app/components/services/global.events.manager.ts deleted file mode 100644 index 4fb04b0..0000000 --- a/ClientApp/ClientApp/app/components/services/global.events.manager.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from "rxjs/BehaviorSubject"; -import { Observable } from "rxjs/Observable"; - -@Injectable() -export class GlobalEventsManager { - - private _showNavBar: BehaviorSubject = new BehaviorSubject(null); - public showNavBarEmitter: Observable = this._showNavBar.asObservable(); - - constructor() {} - - showNavBar(ifShow: boolean) { - this._showNavBar.next(ifShow); - } -} \ No newline at end of file diff --git a/ClientApp/ClientApp/app/components/unauthorized/unauthorized.component.ts b/ClientApp/ClientApp/app/components/unauthorized/unauthorized.component.ts index 4ea66c2..1912fbf 100644 --- a/ClientApp/ClientApp/app/components/unauthorized/unauthorized.component.ts +++ b/ClientApp/ClientApp/app/components/unauthorized/unauthorized.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; import { Location } from '@angular/common'; -import { AuthService } from '../services/auth.service'; @Component({ selector: 'app-unauthorized', @@ -8,7 +7,7 @@ import { AuthService } from '../services/auth.service'; }) export class UnauthorizedComponent implements OnInit { - constructor(private location: Location, private service: AuthService) { + constructor(private location: Location) { } @@ -16,7 +15,7 @@ export class UnauthorizedComponent implements OnInit { } login() { - this.service.startSigninMainWindow(); + //this.service.startSigninMainWindow(); } goback() { diff --git a/ClientApp/package-lock.json b/ClientApp/package-lock.json index 4a13723..3f0216e 100644 --- a/ClientApp/package-lock.json +++ b/ClientApp/package-lock.json @@ -1,5 +1,5 @@ { - "name": "WebApplicationBasic", + "name": "AngularCoreIdentityServerClient", "version": "0.0.0", "lockfileVersion": 1, "dependencies": { @@ -81,6 +81,18 @@ "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, + "angular-auth-oidc-client": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/angular-auth-oidc-client/-/angular-auth-oidc-client-1.3.1.tgz", + "integrity": "sha512-zr/GH2aCwlLnaNnsChtlNjtFs9YTqPENCHKOp8Z/URLOFDsQy53exA20103FFG75OKgBEohXtAYRdlh4dPN3Ag==", + "dependencies": { + "jsrsasign": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-7.2.1.tgz", + "integrity": "sha1-tNgtGdwUglUmMiUSS1zYhVRvbME=" + } + } + }, "angular2-template-loader": { "version": "https://registry.npmjs.org/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz", "integrity": "sha1-wNROkP/w+sleiyPwQ6zaf9HFHXw=" diff --git a/ClientApp/package.json b/ClientApp/package.json index 5233eac..f341a26 100644 --- a/ClientApp/package.json +++ b/ClientApp/package.json @@ -1,5 +1,5 @@ { - "name": "WebApplicationBasic", + "name": "AngularCoreIdentityServerClient", "version": "0.0.0", "dependencies": { "@angular/animations": "4.1.2", @@ -41,7 +41,6 @@ "webpack-hot-middleware": "2.18.0", "webpack-merge": "4.1.0", "zone.js": "0.8.10", - "oidc-client": "1.3.0", - "babel-polyfill": "6.23.0" + "angular-auth-oidc-client": "^1.3.1" } }