diff --git a/CHANGELOG.md b/CHANGELOG.md index 34062a89c..3ec5fe7ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Angular Lib for OpenID Connect/OAuth2 Changelog +### 2021-02-27 Version 11.6.1 + +- Added AutoLoginGuard + - [PR](https://github.com/damienbod/angular-auth-oidc-client/pull/987) +- Updated Azure AD, Azure B2C templates to prompt for select_account (problem with multiple accounts) + ### 2021-02-24 Version 11.6.0 - Added support for OAuth Pushed authorisation requests (PAR) diff --git a/README.md b/README.md index 0b3deae24..97bf212be 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,15 @@ or with yarn - [Guards](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/guards.md) - [Features](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/features.md) + - [Public Events](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/features.md#public-events) - [Auth with a popup](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/authorizing-popup.md) - [Custom Storage](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/features.md#custom-storage) - [Custom parameters](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/features.md#custom-parameters) + - [Auto Login](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/features.md#auto-login) - [Using the OIDC package in a module or a Angular lib](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/features.md#using-the-oidc-package-in-a-module-or-a-angular-lib) - [Delay the loading or pass an existing AuthWellKnownEndpoints config](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/features.md#delay-the-loading-or-pass-an-existing-well-knownopenid-configuration-configuration) + - [Logout](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/logout.md) - [Using and revoking the access token](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/using-access-tokens.md) - [CSP & CORS](https://github.com/damienbod/angular-auth-oidc-client/tree/main/docs/csp-cors-config.md) diff --git a/docs/features.md b/docs/features.md index 94e3ec5aa..812740a6e 100644 --- a/docs/features.md +++ b/docs/features.md @@ -3,6 +3,7 @@ - [Public Events](#public-events) - [Custom Storage](#custom-storage) - [Custom parameters](#custom-parameters) +- [Auto Login](#auto-login) - [Using the OIDC package in a module or a Angular lib](#using-the-oidc-package-in-a-module-or-a-angular-lib) - [Delay the loading or pass an existing AuthWellKnownEndpoints config](#delay-the-loading-or-pass-an-existing-well-knownopenid-configuration-configuration) @@ -88,6 +89,28 @@ Then provide the class in the module: }) ``` +## Auto Login + +If you want to have your app being redirected to the sts automatically without the user clicking any login button you can use the `AutoLoginGuard` provided by the lib. Use it for all the routes you want automatic login to be enabled. + +If you are using auto login _make sure_ to _*not*_ call the `checkAuth()` method in your `app.component.ts`. This will be done by the guard automatically for you. + +Sample routes could be + +```typescript +import { AutoLoginGuard } from 'angular-auth-oidc-client'; + +const appRoutes: Routes = [ + { path: '', pathMatch: 'full', redirectTo: 'home' }, + { path: 'home', component: HomeComponent, canActivate: [AutoLoginGuard] }, + { path: 'protected', component: ProtectedComponent, canActivate: [AutoLoginGuard] }, + { path: 'forbidden', component: ForbiddenComponent, canActivate: [AutoLoginGuard] }, + { path: 'unauthorized', component: UnauthorizedComponent }, +]; +``` + +[src code](../projects/sample-code-flow-auto-login) + ## Custom parameters Custom parameters can be added to the auth request by adding them to the config you are calling the `withConfig(...)` method with. They are provided by diff --git a/docs/guards.md b/docs/guards.md index 350028a88..918f7212d 100644 --- a/docs/guards.md +++ b/docs/guards.md @@ -11,22 +11,22 @@ import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class AuthorizationGuard implements CanActivate { - constructor(private oidcSecurityService: OidcSecurityService, private router: Router) {} - - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.oidcSecurityService.isAuthenticated$.pipe( - map((isAuthorized: boolean) => { - console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized); - - if (!isAuthorized) { - this.router.navigate(['/unauthorized']); - return false; - } - - return true; - }) - ); - } + constructor(private oidcSecurityService: OidcSecurityService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.oidcSecurityService.isAuthenticated$.pipe( + map((isAuthorized: boolean) => { + console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized); + + if (!isAuthorized) { + this.router.navigate(['/unauthorized']); + return false; + } + + return true; + }) + ); + } } ``` diff --git a/docs/logout.md b/docs/logout.md index 47ab61cc9..bb002151a 100644 --- a/docs/logout.md +++ b/docs/logout.md @@ -1,6 +1,6 @@ # Logout -The `logoff()` function sends an endsesion request to the OIDC server, if it is available, or the check session has not sent a changed event. +The `logoff()` function sends an end session request to the OIDC server, if it is available, or the check session has not sent a changed event. ```typescript logout() { diff --git a/docs/migration.md b/docs/migration.md index e6f905dc4..a8dea695c 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -6,58 +6,58 @@ ```typescript export function loadConfig(oidcConfigService: OidcConfigService) { - return () => oidcConfigService.load_using_stsServer('https://localhost:44318'); + return () => oidcConfigService.load_using_stsServer('https://localhost:44318'); } @NgModule({ - imports: [ - HttpClientModule, - AuthModule.forRoot(), - // ... - ], - - declarations: [AppComponent], - - providers: [ - OidcConfigService, - { - provide: APP_INITIALIZER, - useFactory: configureAuth, - deps: [OidcConfigService, HttpClient], - multi: true, - }, - Configuration, - ], - - bootstrap: [AppComponent], + imports: [ + HttpClientModule, + AuthModule.forRoot(), + // ... + ], + + declarations: [AppComponent], + + providers: [ + OidcConfigService, + { + provide: APP_INITIALIZER, + useFactory: configureAuth, + deps: [OidcConfigService, HttpClient], + multi: true, + }, + Configuration, + ], + + bootstrap: [AppComponent], }) export class AppModule { - constructor(private oidcSecurityService: OidcSecurityService, private oidcConfigService: OidcConfigService) { - this.oidcConfigService.onConfigurationLoaded.subscribe((configResult: ConfigResult) => { - const config: OpenIdConfiguration = { - stsServer: 'https://localhost:44318', - redirect_url: 'https://localhost:44395', - client_id: 'angularclient2', - response_type: 'code', - scope: 'dataEventRecords openid profile email', - post_logout_redirect_uri: 'https://localhost:44395/unauthorized', - start_checksession: false, - silent_renew: true, - silent_renew_url: 'https://localhost:44395/silent-renew.html', - post_login_route: '/dm', - forbidden_route: '/unauthorized', - unauthorized_route: '/unauthorized', - log_console_warning_active: true, - log_console_debug_active: false, - max_id_token_iat_offset_allowed_in_seconds: 10, - history_cleanup_off: true, - // iss_validation_off: false - // disable_iat_offset_validation: true - }; - - this.oidcSecurityService.setupModule(config, configResult.authWellknownEndpoints); - }); - } + constructor(private oidcSecurityService: OidcSecurityService, private oidcConfigService: OidcConfigService) { + this.oidcConfigService.onConfigurationLoaded.subscribe((configResult: ConfigResult) => { + const config: OpenIdConfiguration = { + stsServer: 'https://localhost:44318', + redirect_url: 'https://localhost:44395', + client_id: 'angularclient2', + response_type: 'code', + scope: 'dataEventRecords openid profile email', + post_logout_redirect_uri: 'https://localhost:44395/unauthorized', + start_checksession: false, + silent_renew: true, + silent_renew_url: 'https://localhost:44395/silent-renew.html', + post_login_route: '/dm', + forbidden_route: '/unauthorized', + unauthorized_route: '/unauthorized', + log_console_warning_active: true, + log_console_debug_active: false, + max_id_token_iat_offset_allowed_in_seconds: 10, + history_cleanup_off: true, + // iss_validation_off: false + // disable_iat_offset_validation: true + }; + + this.oidcSecurityService.setupModule(config, configResult.authWellknownEndpoints); + }); + } } ``` @@ -69,37 +69,37 @@ import { NgModule, APP_INITIALIZER } from '@angular/core'; import { AuthModule, OidcConfigService } from 'angular-auth-oidc-client'; export function configureAuth(oidcConfigService: OidcConfigService) { - return () => - oidcConfigService.withConfig({ - stsServer: 'https://localhost:44318', - redirectUrl: window.location.origin, - postLogoutRedirectUri: 'https://localhost:44395/unauthorized', - clientId: 'angularclient2', - scope: 'dataEventRecords openid profile email', - responseType: 'code', - silentRenew: true, - silentRenewUrl: `${window.location.origin}/silent-renew.html`, - renewTimeBeforeTokenExpiresInSeconds: 10, - logLevel: LogLevel.Debug, - postLoginRoute: '/dm', - forbiddenRoute: '/unauthorized', - unauthorizedRoute: '/unauthorized', - historyCleanupOff: true, - }); + return () => + oidcConfigService.withConfig({ + stsServer: 'https://localhost:44318', + redirectUrl: window.location.origin, + postLogoutRedirectUri: 'https://localhost:44395/unauthorized', + clientId: 'angularclient2', + scope: 'dataEventRecords openid profile email', + responseType: 'code', + silentRenew: true, + silentRenewUrl: `${window.location.origin}/silent-renew.html`, + renewTimeBeforeTokenExpiresInSeconds: 10, + logLevel: LogLevel.Debug, + postLoginRoute: '/dm', + forbiddenRoute: '/unauthorized', + unauthorizedRoute: '/unauthorized', + historyCleanupOff: true, + }); } @NgModule({ - imports: [AuthModule.forRoot()], - // declarations, etc. - providers: [ - OidcConfigService, - { - provide: APP_INITIALIZER, - useFactory: configureAuth, - deps: [OidcConfigService], - multi: true, - }, - ], + imports: [AuthModule.forRoot()], + // declarations, etc. + providers: [ + OidcConfigService, + { + provide: APP_INITIALIZER, + useFactory: configureAuth, + deps: [OidcConfigService], + multi: true, + }, + ], }) export class AppModule {} ``` @@ -114,58 +114,58 @@ import { NgModule, APP_INITIALIZER } from '@angular/core'; import { AuthModule, OidcConfigService, ConfigResult, OpenIdConfiguration } from 'angular-auth-oidc-client'; export function loadConfig(oidcConfigService: OidcConfigService) { - return () => oidcConfigService.load(`${window.location.origin}/api/ClientAppSettings`); + return () => oidcConfigService.load(`${window.location.origin}/api/ClientAppSettings`); } @NgModule({ - imports: [ - HttpClientModule, - AuthModule.forRoot(), - //... - ], - - declarations: [AppComponent], - - providers: [ - OidcSecurityService, - OidcConfigService, - { - provide: APP_INITIALIZER, - useFactory: loadConfig, - deps: [OidcConfigService], - multi: true, - }, - Configuration, - ], - - bootstrap: [AppComponent], + imports: [ + HttpClientModule, + AuthModule.forRoot(), + //... + ], + + declarations: [AppComponent], + + providers: [ + OidcSecurityService, + OidcConfigService, + { + provide: APP_INITIALIZER, + useFactory: loadConfig, + deps: [OidcConfigService], + multi: true, + }, + Configuration, + ], + + bootstrap: [AppComponent], }) export class AppModule { - constructor(private oidcSecurityService: OidcSecurityService, private oidcConfigService: OidcConfigService) { - 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: 'https://localhost:44311/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 - }; - - this.oidcSecurityService.setupModule(config, configResult.authWellknownEndpoints); - }); - } + constructor(private oidcSecurityService: OidcSecurityService, private oidcConfigService: OidcConfigService) { + 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: 'https://localhost:44311/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 + }; + + this.oidcSecurityService.setupModule(config, configResult.authWellknownEndpoints); + }); + } } ``` @@ -178,51 +178,51 @@ import { AuthModule, OidcConfigService } from 'angular-auth-oidc-client'; import { map, switchMap } from 'rxjs/operators'; export function configureAuth(oidcConfigService: OidcConfigService, httpClient: HttpClient) { - const setupAction$ = httpClient.get(`${window.location.origin}/api/ClientAppSettings`).pipe( - map((customConfig) => { - return { - stsServer: customConfig.stsServer, - redirectUrl: customConfig.redirect_url, - clientId: customConfig.client_id, - responseType: customConfig.response_type, - scope: customConfig.scope, - postLogoutRedirectUri: customConfig.post_logout_redirect_uri, - startCheckSession: customConfig.start_checksession, - silentRenew: true, - silentRenewUrl: customConfig.redirect_url + '/silent-renew.html', - postLoginRoute: customConfig.startup_route, - forbiddenRoute: customConfig.forbidden_route, - unauthorizedRoute: customConfig.unauthorized_route, - logLevel: 0, // LogLevel.Debug, // customConfig.logLevel - maxIdTokenIatOffsetAllowedInSeconds: customConfig.max_id_token_iat_offset_allowed_in_seconds, - historyCleanupOff: true, - // autoUserinfo: false, - }; - }), - switchMap((config) => oidcConfigService.withConfig(config)) - ); - - return () => setupAction$.toPromise(); + const setupAction$ = httpClient.get(`${window.location.origin}/api/ClientAppSettings`).pipe( + map((customConfig) => { + return { + stsServer: customConfig.stsServer, + redirectUrl: customConfig.redirect_url, + clientId: customConfig.client_id, + responseType: customConfig.response_type, + scope: customConfig.scope, + postLogoutRedirectUri: customConfig.post_logout_redirect_uri, + startCheckSession: customConfig.start_checksession, + silentRenew: true, + silentRenewUrl: customConfig.redirect_url + '/silent-renew.html', + postLoginRoute: customConfig.startup_route, + forbiddenRoute: customConfig.forbidden_route, + unauthorizedRoute: customConfig.unauthorized_route, + logLevel: 0, // LogLevel.Debug, // customConfig.logLevel + maxIdTokenIatOffsetAllowedInSeconds: customConfig.max_id_token_iat_offset_allowed_in_seconds, + historyCleanupOff: true, + // autoUserinfo: false, + }; + }), + switchMap((config) => oidcConfigService.withConfig(config)) + ); + + return () => setupAction$.toPromise(); } @NgModule({ - imports: [ - HttpClientModule, - AuthModule.forRoot(), - // ... - ], + imports: [ + HttpClientModule, + AuthModule.forRoot(), // ... - providers: [ - OidcConfigService, - { - provide: APP_INITIALIZER, - useFactory: configureAuth, - deps: [OidcConfigService, HttpClient], - multi: true, - }, - ], - - bootstrap: [AppComponent], + ], + // ... + providers: [ + OidcConfigService, + { + provide: APP_INITIALIZER, + useFactory: configureAuth, + deps: [OidcConfigService, HttpClient], + multi: true, + }, + ], + + bootstrap: [AppComponent], }) export class AppModule {} ``` @@ -237,40 +237,40 @@ import { Subscription } from 'rxjs'; import { OidcSecurityService } from 'angular-auth-oidc-client'; @Component({ - selector: 'app-component', - templateUrl: 'app.component.html', - styleUrls: ['./app.component.scss'], + selector: 'app-component', + templateUrl: 'app.component.html', + styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnInit, OnDestroy { - isAuthorizedSubscription: Subscription | undefined; - isAuthorized = false; - - constructor(public oidcSecurityService: OidcSecurityService) { - if (this.oidcSecurityService.moduleSetup) { - this.doCallbackLogicIfRequired(); - } else { - this.oidcSecurityService.onModuleSetup.subscribe(() => { - this.doCallbackLogicIfRequired(); - }); - } + isAuthorizedSubscription: Subscription | undefined; + isAuthorized = false; + + constructor(public oidcSecurityService: OidcSecurityService) { + if (this.oidcSecurityService.moduleSetup) { + this.doCallbackLogicIfRequired(); + } else { + this.oidcSecurityService.onModuleSetup.subscribe(() => { + this.doCallbackLogicIfRequired(); + }); } + } - ngOnInit() { - this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe((isAuthorized: boolean) => { - this.isAuthorized = isAuthorized; - }); - } + ngOnInit() { + this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe((isAuthorized: boolean) => { + this.isAuthorized = isAuthorized; + }); + } - ngOnDestroy(): void { - if (this.isAuthorizedSubscription) { - this.isAuthorizedSubscription.unsubscribe(); - } + ngOnDestroy(): void { + if (this.isAuthorizedSubscription) { + this.isAuthorizedSubscription.unsubscribe(); } + } - private doCallbackLogicIfRequired() { - // Will do a callback, if the url has a code and state parameter. - this.oidcSecurityService.authorizedCallbackWithCode(window.location.toString()); - } + private doCallbackLogicIfRequired() { + // Will do a callback, if the url has a code and state parameter. + this.oidcSecurityService.authorizedCallbackWithCode(window.location.toString()); + } } ``` @@ -282,20 +282,20 @@ import { Observable } from 'rxjs'; import { OidcSecurityService } from 'angular-auth-oidc-client'; @Component({ - selector: 'app-component', - templateUrl: 'app.component.html', - styleUrls: ['./app.component.scss'], + selector: 'app-component', + templateUrl: 'app.component.html', + styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnInit { - isAuthenticated$: Observable; + isAuthenticated$: Observable; - constructor(public oidcSecurityService: OidcSecurityService) {} + constructor(public oidcSecurityService: OidcSecurityService) {} - ngOnInit() { - this.isAuthenticated$ = this.oidcSecurityService.isAuthenticated$; + ngOnInit() { + this.isAuthenticated$ = this.oidcSecurityService.isAuthenticated$; - this.oidcSecurityService.checkAuth().subscribe((isAuthenticated) => console.log('app authenticated', isAuthenticated)); - } + this.oidcSecurityService.checkAuth().subscribe((isAuthenticated) => console.log('app authenticated', isAuthenticated)); + } } ``` @@ -305,7 +305,7 @@ export class AppComponent implements OnInit { ```typescript this.oidcSecurityService.getIsAuthorized().subscribe((isAuthenticated: boolean) => { - // work with `isAuthenticated` + // work with `isAuthenticated` }); ``` @@ -313,7 +313,7 @@ this.oidcSecurityService.getIsAuthorized().subscribe((isAuthenticated: boolean) ```typescript this.oidcSecurityService.isAuthenticated$.subscribe((isAuthenticated: boolean) => { - // work with `isAuthenticated` + // work with `isAuthenticated` }); ``` @@ -323,7 +323,7 @@ this.oidcSecurityService.isAuthenticated$.subscribe((isAuthenticated: boolean) = ```typescript this.oidcSecurityService.getUserData().subscribe((userData: any) => { - // work with `userData` + // work with `userData` }); ``` @@ -331,6 +331,6 @@ this.oidcSecurityService.getUserData().subscribe((userData: any) => { ```typescript this.oidcSecurityService.userData$.subscribe((userData: any) => { - // work with `userData` + // work with `userData` }); ``` diff --git a/docs/public-api.md b/docs/public-api.md index dd0e220ea..b8f34c255 100644 --- a/docs/public-api.md +++ b/docs/public-api.md @@ -20,14 +20,14 @@ this.userData$ = this.oidcSecurityService.userData$; ```json { - "sub": "...", - "preferred_username": "john@doe.org", - "name": "john@doe.org", - "email": "john@doe.org", - "email_verified": false, - "given_name": "john@doe.org", - "role": "user", - "amr": "pwd" + "sub": "...", + "preferred_username": "john@doe.org", + "name": "john@doe.org", + "email": "john@doe.org", + "email_verified": false, + "given_name": "john@doe.org", + "role": "user", + "amr": "pwd" } ``` @@ -101,8 +101,8 @@ You can pass optional `AuthOptions` ```ts export interface AuthOptions { - customParams?: { [key: string]: string | number | boolean }; - urlHandler?(url: string): any; + customParams?: { [key: string]: string | number | boolean }; + urlHandler?(url: string): any; } ``` @@ -153,12 +153,12 @@ The `checkAuthIncludingServer` can be used to check the server for an authentica ```typescript export class AppComponent implements OnInit { - constructor(public oidcSecurityService: OidcSecurityService) {} + constructor(public oidcSecurityService: OidcSecurityService) {} - ngOnInit() { - this.oidcSecurityService.checkAuthIncludingServer().subscribe((isAuthenticated) => { - console.log('app authenticated', isAuthenticated); - }); - } + ngOnInit() { + this.oidcSecurityService.checkAuthIncludingServer().subscribe((isAuthenticated) => { + console.log('app authenticated', isAuthenticated); + }); + } } ``` diff --git a/docs/quickstart.md b/docs/quickstart.md index dca64f89a..a7365260f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -39,36 +39,36 @@ import { AuthModule, LogLevel, OidcConfigService } from 'angular-auth-oidc-clien // ... export function configureAuth(oidcConfigService: OidcConfigService) { - return () => - oidcConfigService.withConfig({ - stsServer: '', - redirectUrl: window.location.origin, - postLogoutRedirectUri: window.location.origin, - clientId: 'angularClient', - scope: 'openid profile email', - responseType: 'code', - silentRenew: true, - silentRenewUrl: `${window.location.origin}/silent-renew.html`, - logLevel: LogLevel.Debug, - }); + return () => + oidcConfigService.withConfig({ + stsServer: '', + redirectUrl: window.location.origin, + postLogoutRedirectUri: window.location.origin, + clientId: 'angularClient', + scope: 'openid profile email', + responseType: 'code', + silentRenew: true, + silentRenewUrl: `${window.location.origin}/silent-renew.html`, + logLevel: LogLevel.Debug, + }); } @NgModule({ + // ... + imports: [ // ... - imports: [ - // ... - AuthModule.forRoot(), - ], - providers: [ - OidcConfigService, - { - provide: APP_INITIALIZER, - useFactory: configureAuth, - deps: [OidcConfigService], - multi: true, - }, - ], - // ... + AuthModule.forRoot(), + ], + providers: [ + OidcConfigService, + { + provide: APP_INITIALIZER, + useFactory: configureAuth, + deps: [OidcConfigService], + multi: true, + }, + ], + // ... }) export class AppModule {} ``` diff --git a/docs/samples.md b/docs/samples.md index ac70ce43f..cb2f1d39b 100644 --- a/docs/samples.md +++ b/docs/samples.md @@ -58,8 +58,6 @@ The example logins the user in directly without a login click using the Code Flo [auto-login.component.ts](../projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.ts) -[guard.ts](../projects/sample-code-flow-auto-login/src/app/authorization.guard.ts) - ## Code Flow with PKCE basic with silent renew The is the basic example of the Code Flow with PKCE. diff --git a/docs/silent-renew.md b/docs/silent-renew.md index bc519e336..a1f1afb5d 100644 --- a/docs/silent-renew.md +++ b/docs/silent-renew.md @@ -1,9 +1,9 @@ # Silent Renew -- [Silent Renew Code Flow with PKCE](#silent-renew-code-flow-with-pkce) -- [Silent Renew Code Flow with PKCE with refresh tokens](#silent-renew-code-flow-with-pkce-with-refresh-tokens) -- [Silent Renew Implicit Flow](#silent-renew-implicit-flow) -- [Secure Token Server CSP and CORS](#secure-token-server-csp-and-cors) +- [Silent Renew Code Flow with PKCE](#silent-renew-code-flow-with-pkce) +- [Silent Renew Code Flow with PKCE with refresh tokens](#silent-renew-code-flow-with-pkce-with-refresh-tokens) +- [Silent Renew Implicit Flow](#silent-renew-implicit-flow) +- [Secure Token Server CSP and CORS](#secure-token-server-csp-and-cors) When silent renew is enabled, a DOM event will be automatically installed in the application's host window. The event `oidc-silent-renew-message` accepts a `CustomEvent` instance with the token returned from the OAuth server diff --git a/docs/using-access-tokens.md b/docs/using-access-tokens.md index 14cf54d7c..6f6570ca7 100644 --- a/docs/using-access-tokens.md +++ b/docs/using-access-tokens.md @@ -18,9 +18,9 @@ import { HttpHeaders } from '@angular/common/http'; const token = this.oidcSecurityServices.getToken(); const httpOptions = { - headers: new HttpHeaders({ - Authorization: 'Bearer ' + token, - }), + headers: new HttpHeaders({ + Authorization: 'Bearer ' + token, + }), }; ``` @@ -34,11 +34,11 @@ You can configure the routes you want to send a token with in the configuration ```typescript export function configureAuth(oidcConfigService: OidcConfigService) { - return () => - oidcConfigService.withConfig({ - // ... - secureRoutes: ['https://my-secure-url.com/', 'https://my-second-secure-url.com/'], - }); + return () => + oidcConfigService.withConfig({ + // ... + secureRoutes: ['https://my-secure-url.com/', 'https://my-second-secure-url.com/'], + }); } ``` diff --git a/package-lock.json b/package-lock.json index 7e5ea2624..8b9a3ff7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "angular-auth-oidc-client", - "version": "11.6.0", + "version": "11.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1c246cd9d..e38090082 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "bugs": { "url": "https://github.com/damienbod/angular-auth-oidc-client/issues" }, - "version": "11.6.0", + "version": "11.6.1", "scripts": { "ng": "ng", "build": "npm run build-lib", diff --git a/projects/angular-auth-oidc-client/package.json b/projects/angular-auth-oidc-client/package.json index 42bed7f92..a57977fb1 100644 --- a/projects/angular-auth-oidc-client/package.json +++ b/projects/angular-auth-oidc-client/package.json @@ -37,7 +37,7 @@ "authorization" ], "license": "MIT", - "version": "11.6.0", + "version": "11.6.1", "description": "Angular Lib for OpenID Connect & OAuth2", "schematics": "./schematics/collection.json" } diff --git a/projects/angular-auth-oidc-client/src/lib/angular-auth-oidc-client.ts b/projects/angular-auth-oidc-client/src/lib/angular-auth-oidc-client.ts index 2639aef51..02282d459 100644 --- a/projects/angular-auth-oidc-client/src/lib/angular-auth-oidc-client.ts +++ b/projects/angular-auth-oidc-client/src/lib/angular-auth-oidc-client.ts @@ -7,6 +7,7 @@ export * from './config/auth-well-known-endpoints'; export * from './config/config.service'; export * from './config/openid-configuration'; export * from './config/public-configuration'; +export * from './guards/auto-login.guard'; export * from './interceptor/auth.interceptor'; export * from './logging/log-level'; export * from './logging/logger.service'; diff --git a/projects/angular-auth-oidc-client/src/lib/callback/callback.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/callback/callback.service.spec.ts index d7a57fcfc..d96a4ca7a 100644 --- a/projects/angular-auth-oidc-client/src/lib/callback/callback.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/callback/callback.service.spec.ts @@ -1,4 +1,4 @@ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; @@ -23,7 +23,7 @@ describe('Callbackservice ', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientModule, RouterTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule], providers: [ CallbackService, { provide: UrlService, useClass: UrlServiceMock }, diff --git a/projects/angular-auth-oidc-client/src/lib/callback/code-flow-callback.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/callback/code-flow-callback.service.spec.ts index 1d0866a62..3f076d82f 100644 --- a/projects/angular-auth-oidc-client/src/lib/callback/code-flow-callback.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/callback/code-flow-callback.service.spec.ts @@ -1,4 +1,4 @@ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed, waitForAsync } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; @@ -9,7 +9,6 @@ import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataServiceMock } from '../flows/flows-data.service-mock'; import { FlowsService } from '../flows/flows.service'; import { FlowsServiceMock } from '../flows/flows.service-mock'; -import { JwtKeys } from '../validation/jwtkeys'; import { CodeFlowCallbackService } from './code-flow-callback.service'; import { IntervallService } from './intervall.service'; @@ -23,7 +22,7 @@ describe('CodeFlowCallbackService ', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientModule, RouterTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule], providers: [ CodeFlowCallbackService, { provide: FlowsService, useClass: FlowsServiceMock }, @@ -64,7 +63,7 @@ describe('CodeFlowCallbackService ', () => { sessionState: null, authResult: null, isRenewProcess: true, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: '', }; @@ -88,7 +87,7 @@ describe('CodeFlowCallbackService ', () => { sessionState: null, authResult: null, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: '', }; diff --git a/projects/angular-auth-oidc-client/src/lib/callback/implicit-flow-callback.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/callback/implicit-flow-callback.service.spec.ts index b62ad4d10..44d6edec1 100644 --- a/projects/angular-auth-oidc-client/src/lib/callback/implicit-flow-callback.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/callback/implicit-flow-callback.service.spec.ts @@ -1,4 +1,4 @@ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed, waitForAsync } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; @@ -9,13 +9,12 @@ import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataServiceMock } from '../flows/flows-data.service-mock'; import { FlowsService } from '../flows/flows.service'; import { FlowsServiceMock } from '../flows/flows.service-mock'; -import { JwtKeys } from '../validation/jwtkeys'; import { ImplicitFlowCallbackService } from './implicit-flow-callback.service'; import { IntervallService } from './intervall.service'; describe('ImplicitFlowCallbackService ', () => { let implicitFlowCallbackService: ImplicitFlowCallbackService; - let intervallService: IntervallService; + let intervalService: IntervallService; let flowsService: FlowsService; let configurationProvider: ConfigurationProvider; let flowsDataService: FlowsDataService; @@ -23,7 +22,7 @@ describe('ImplicitFlowCallbackService ', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientModule, RouterTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule], providers: [ ImplicitFlowCallbackService, { provide: FlowsService, useClass: FlowsServiceMock }, @@ -37,7 +36,7 @@ describe('ImplicitFlowCallbackService ', () => { beforeEach(() => { implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService); configurationProvider = TestBed.inject(ConfigurationProvider); - intervallService = TestBed.inject(IntervallService); + intervalService = TestBed.inject(IntervallService); flowsDataService = TestBed.inject(FlowsDataService); flowsService = TestBed.inject(FlowsService); router = TestBed.inject(Router); @@ -64,7 +63,7 @@ describe('ImplicitFlowCallbackService ', () => { sessionState: null, authResult: null, isRenewProcess: true, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: '', }; @@ -88,7 +87,7 @@ describe('ImplicitFlowCallbackService ', () => { sessionState: null, authResult: null, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: '', }; @@ -106,11 +105,11 @@ describe('ImplicitFlowCallbackService ', () => { ); it( - 'resetSilentRenewRunning and stopPeriodicallTokenCheck in case of error', + 'resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', waitForAsync(() => { spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(throwError('error')); const resetSilentRenewRunningSpy = spyOn(flowsDataService, 'resetSilentRenewRunning'); - const stopPeriodicallTokenCheckSpy = spyOn(intervallService, 'stopPeriodicallTokenCheck'); + const stopPeriodicallyTokenCheckSpy = spyOn(intervalService, 'stopPeriodicallTokenCheck'); spyOnProperty(configurationProvider, 'openIDConfiguration').and.returnValue({ triggerAuthorizationResultEvent: false, @@ -119,7 +118,7 @@ describe('ImplicitFlowCallbackService ', () => { implicitFlowCallbackService.authorizedImplicitFlowCallback('some-hash').subscribe({ error: (err) => { expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); - expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); + expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); expect(err).toBeTruthy(); }, }); @@ -133,7 +132,7 @@ describe('ImplicitFlowCallbackService ', () => { spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(throwError('error')); const resetSilentRenewRunningSpy = spyOn(flowsDataService, 'resetSilentRenewRunning'); - const stopPeriodicallTokenCheckSpy = spyOn(intervallService, 'stopPeriodicallTokenCheck'); + const stopPeriodicallTokenCheckSpy = spyOn(intervalService, 'stopPeriodicallTokenCheck'); const routerSpy = spyOn(router, 'navigate'); spyOnProperty(configurationProvider, 'openIDConfiguration').and.returnValue({ diff --git a/projects/angular-auth-oidc-client/src/lib/callback/periodically-token-check.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/callback/periodically-token-check.service.spec.ts index 2cbfb3720..7c5df08cb 100644 --- a/projects/angular-auth-oidc-client/src/lib/callback/periodically-token-check.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/callback/periodically-token-check.service.spec.ts @@ -1,4 +1,4 @@ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { of, Subscription, throwError } from 'rxjs'; @@ -39,7 +39,7 @@ describe('PeriodicallyTokenCheckService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientModule, RouterTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule], providers: [ { provide: RefreshSessionRefreshTokenService, useClass: RefreshSessionRefreshTokenServiceMock }, { provide: ResetAuthDataService, useClass: ResetAuthDataServiceMock }, diff --git a/projects/angular-auth-oidc-client/src/lib/callback/refresh-session-refresh-token.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/callback/refresh-session-refresh-token.service.spec.ts index 8bcfb936b..e2410573b 100644 --- a/projects/angular-auth-oidc-client/src/lib/callback/refresh-session-refresh-token.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/callback/refresh-session-refresh-token.service.spec.ts @@ -1,4 +1,4 @@ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { of, throwError } from 'rxjs'; @@ -19,7 +19,7 @@ describe('RefreshSessionRefreshTokenService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientModule, RouterTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule], providers: [ RefreshSessionRefreshTokenService, { provide: LoggerService, useClass: LoggerServiceMock }, diff --git a/projects/angular-auth-oidc-client/src/lib/check-auth.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/check-auth.service.spec.ts index d48294424..bb86bdafd 100644 --- a/projects/angular-auth-oidc-client/src/lib/check-auth.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/check-auth.service.spec.ts @@ -1,4 +1,4 @@ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed, waitForAsync } from '@angular/core/testing'; import { BrowserModule } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; @@ -36,7 +36,7 @@ describe('CheckAuthService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [BrowserModule, HttpClientModule, RouterTestingModule, AuthModule.forRoot()], + imports: [BrowserModule, HttpClientTestingModule, RouterTestingModule, AuthModule.forRoot()], providers: [ CheckSessionService, { provide: SilentRenewService, useClass: SilentRenewServiceMock }, diff --git a/projects/angular-auth-oidc-client/src/lib/check-auth.service.ts b/projects/angular-auth-oidc-client/src/lib/check-auth.service.ts index 50ea3847c..b72413bbf 100644 --- a/projects/angular-auth-oidc-client/src/lib/check-auth.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/check-auth.service.ts @@ -66,7 +66,10 @@ export class CheckAuthService { return isAuthenticated; }), - catchError(() => of(false)) + catchError((error) => { + this.loggerService.logError(error); + return of(false); + }) ); } diff --git a/projects/angular-auth-oidc-client/src/lib/flows/callback-handling/code-flow-callback-handler.service.ts b/projects/angular-auth-oidc-client/src/lib/flows/callback-handling/code-flow-callback-handler.service.ts index 8c529df79..532ff12d0 100644 --- a/projects/angular-auth-oidc-client/src/lib/flows/callback-handling/code-flow-callback-handler.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/flows/callback-handling/code-flow-callback-handler.service.ts @@ -58,10 +58,9 @@ export class CodeFlowCallbackHandlerService { // STEP 2 Code Flow // Code Flow Silent Renew starts here codeFlowCodeRequest(callbackContext: CallbackContext): Observable { - const isStateCorrect = this.tokenValidationService.validateStateFromHashCallback( - callbackContext.state, - this.flowsDataService.getAuthStateControl() - ); + const authStateControl = this.flowsDataService.getAuthStateControl(); + + const isStateCorrect = this.tokenValidationService.validateStateFromHashCallback(callbackContext.state, authStateControl); if (!isStateCorrect) { this.loggerService.logWarning('codeFlowCodeRequest incorrect state'); diff --git a/projects/angular-auth-oidc-client/src/lib/guards/auto-login.guard.spec.ts b/projects/angular-auth-oidc-client/src/lib/guards/auto-login.guard.spec.ts new file mode 100644 index 000000000..0703f5b66 --- /dev/null +++ b/projects/angular-auth-oidc-client/src/lib/guards/auto-login.guard.spec.ts @@ -0,0 +1,127 @@ +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { Router, RouterStateSnapshot } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of } from 'rxjs'; +import { AuthStateService } from '../authState/auth-state.service'; +import { AuthStateServiceMock } from '../authState/auth-state.service-mock'; +import { CheckAuthService } from '../check-auth.service'; +import { CheckAuthServiceMock } from '../check-auth.service-mock'; +import { LoginService } from '../login/login.service'; +import { LoginServiceMock } from '../login/login.service-mock'; +import { AutoLoginGuard } from './auto-login.guard'; + +describe(`AutoLoginGuard`, () => { + let autoLoginGuard: AutoLoginGuard; + let checkAuthService: CheckAuthService; + let loginService: LoginService; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + providers: [ + { provide: AuthStateService, useClass: AuthStateServiceMock }, + { + provide: LoginService, + useClass: LoginServiceMock, + }, + { + provide: CheckAuthService, + useClass: CheckAuthServiceMock, + }, + ], + }); + }); + + beforeEach(() => { + autoLoginGuard = TestBed.inject(AutoLoginGuard); + checkAuthService = TestBed.inject(CheckAuthService); + router = TestBed.inject(Router); + loginService = TestBed.inject(LoginService); + + localStorage.clear(); + }); + + it('should create', () => { + expect(autoLoginGuard).toBeTruthy(); + }); + + describe('canActivate', () => { + it( + 'should call checkAuth', + waitForAsync(() => { + const checkAuthServiceSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue(of(null)); + + autoLoginGuard.canActivate(null, { url: 'some-url' } as RouterStateSnapshot).subscribe(() => { + expect(checkAuthServiceSpy).toHaveBeenCalledTimes(1); + }); + }) + ); + + it( + 'should call loginService.login() when not authorized', + waitForAsync(() => { + spyOn(checkAuthService, 'checkAuth').and.returnValue(of(null)); + const loginSpy = spyOn(loginService, 'login'); + + autoLoginGuard.canActivate(null, { url: 'some-url' } as RouterStateSnapshot).subscribe(() => { + expect(loginSpy).toHaveBeenCalledTimes(1); + }); + }) + ); + + it( + 'should return false when not authorized', + waitForAsync(() => { + spyOn(checkAuthService, 'checkAuth').and.returnValue(of(null)); + + autoLoginGuard.canActivate(null, { url: 'some-url' } as RouterStateSnapshot).subscribe((result) => { + expect(result).toBe(false); + }); + }) + ); + + it( + 'if no route is stored, setItem on localStorage is called', + waitForAsync(() => { + spyOn(checkAuthService, 'checkAuth').and.returnValue(of(null)); + const localStorageSpy = spyOn(localStorage, 'setItem'); + + autoLoginGuard.canActivate(null, { url: 'some-url' } as RouterStateSnapshot).subscribe((result) => { + expect(localStorageSpy).toHaveBeenCalledOnceWith('redirect', 'some-url'); + }); + }) + ); + + it( + 'returns true if authorized', + waitForAsync(() => { + spyOn(checkAuthService, 'checkAuth').and.returnValue(of(true)); + const localStorageSpy = spyOn(localStorage, 'setItem'); + + autoLoginGuard.canActivate(null, { url: 'some-url' } as RouterStateSnapshot).subscribe((result) => { + expect(result).toBe(true); + expect(localStorageSpy).not.toHaveBeenCalled(); + }); + }) + ); + + it( + 'if authorized and stored route exists: remove item, navigate to route and return true', + waitForAsync(() => { + spyOn(checkAuthService, 'checkAuth').and.returnValue(of(true)); + spyOn(localStorage, 'getItem').and.returnValue('stored-route'); + const localStorageSpy = spyOn(localStorage, 'removeItem'); + const routerSpy = spyOn(router, 'navigate'); + const loginSpy = spyOn(loginService, 'login'); + + autoLoginGuard.canActivate(null, { url: 'some-url' } as RouterStateSnapshot).subscribe((result) => { + expect(result).toBe(true); + expect(localStorageSpy).toHaveBeenCalledOnceWith('redirect'); + expect(routerSpy).toHaveBeenCalledOnceWith(['stored-route']); + expect(loginSpy).not.toHaveBeenCalled(); + }); + }) + ); + }); +}); diff --git a/projects/angular-auth-oidc-client/src/lib/guards/auto-login.guard.ts b/projects/angular-auth-oidc-client/src/lib/guards/auto-login.guard.ts new file mode 100644 index 000000000..cceffcece --- /dev/null +++ b/projects/angular-auth-oidc-client/src/lib/guards/auto-login.guard.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { CheckAuthService } from '../check-auth.service'; +import { LoginService } from '../login/login.service'; + +const STORAGE_KEY = 'redirect'; + +@Injectable({ providedIn: 'root' }) +export class AutoLoginGuard implements CanActivate { + constructor(private checkAuthService: CheckAuthService, private loginService: LoginService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.checkAuthService.checkAuth().pipe( + map((isAuthorized: boolean) => { + const storedRoute = localStorage.getItem(STORAGE_KEY); + + if (isAuthorized) { + if (storedRoute) { + localStorage.removeItem(STORAGE_KEY); + this.router.navigate([storedRoute]); + } + return true; + } + + localStorage.setItem(STORAGE_KEY, state.url); + this.loginService.login(); + return false; + }) + ); + } +} diff --git a/projects/angular-auth-oidc-client/src/lib/login/par/par-login.service.ts b/projects/angular-auth-oidc-client/src/lib/login/par/par-login.service.ts index ee65259fe..497a6b413 100644 --- a/projects/angular-auth-oidc-client/src/lib/login/par/par-login.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/login/par/par-login.service.ts @@ -109,7 +109,8 @@ export class ParLoginService { this.popupService.openPopUp(url, popupOptions); - return this.popupService.receivedUrl$.pipe(take(1), + return this.popupService.receivedUrl$.pipe( + take(1), switchMap((receivedUrl: string) => this.checkAuthService.checkAuth(receivedUrl)), map((isAuthenticated) => ({ isAuthenticated, diff --git a/projects/angular-auth-oidc-client/src/lib/login/popup/popup-login.service.ts b/projects/angular-auth-oidc-client/src/lib/login/popup/popup-login.service.ts index 45f9444fd..6771d73ac 100644 --- a/projects/angular-auth-oidc-client/src/lib/login/popup/popup-login.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/login/popup/popup-login.service.ts @@ -53,7 +53,8 @@ export class PopUpLoginService { this.popupService.openPopUp(authUrl, popupOptions); - return this.popupService.receivedUrl$.pipe(take(1), + return this.popupService.receivedUrl$.pipe( + take(1), switchMap((url: string) => this.checkAuthService.checkAuth(url)), map((isAuthenticated) => ({ isAuthenticated, diff --git a/projects/angular-auth-oidc-client/src/lib/validation/jwtkeys.ts b/projects/angular-auth-oidc-client/src/lib/validation/jwtkeys.ts index b177fd4ec..08fde6999 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/jwtkeys.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/jwtkeys.ts @@ -1,13 +1,13 @@ -export class JwtKeys { - keys: JwtKey[] = []; +export interface JwtKeys { + keys: JwtKey[]; } -export class JwtKey { - kty = ''; - use = ''; - kid = ''; - x5t = ''; - e = ''; - n = ''; - x5c: any[] = []; +export interface JwtKey { + kty: string; + use: string; + kid: string; + x5t: string; + e: string; + n: string; + x5c: any[]; } diff --git a/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts index 7e5fb4ec6..4c76b77a2 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable max-len */ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { BrowserModule } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; @@ -13,7 +13,6 @@ import { LoggerServiceMock } from '../logging/logger.service-mock'; import { StoragePersistanceService } from '../storage/storage-persistance.service'; import { StoragePersistanceServiceMock } from '../storage/storage-persistance.service-mock'; import { TokenHelperService } from '../utils/tokenHelper/oidc-token-helper.service'; -import { JwtKeys } from './jwtkeys'; import { StateValidationService } from './state-validation.service'; import { TokenValidationService } from './token-validation.service'; import { ValidationResult } from './validation-result'; @@ -30,7 +29,7 @@ describe('State Validation Service', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [BrowserModule, HttpClientModule, RouterTestingModule, AuthModule.forRoot()], + imports: [BrowserModule, HttpClientTestingModule, RouterTestingModule, AuthModule.forRoot()], providers: [ StateValidationService, TokenValidationService, @@ -118,7 +117,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -179,7 +178,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -212,7 +211,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -251,7 +250,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -294,7 +293,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -341,7 +340,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -391,7 +390,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -443,7 +442,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -497,7 +496,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -543,7 +542,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -592,7 +591,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -639,7 +638,7 @@ describe('State Validation Service', () => { id_token: 'id_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -698,7 +697,7 @@ describe('State Validation Service', () => { id_token: '', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, existingIdToken: null, }; @@ -752,7 +751,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -818,7 +817,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -884,7 +883,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -950,7 +949,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -1016,7 +1015,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -1082,7 +1081,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -1148,7 +1147,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -1214,7 +1213,7 @@ describe('State Validation Service', () => { id_token: idToken, }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; @@ -1258,7 +1257,7 @@ describe('State Validation Service', () => { error: 'access_tokenTEST', }, isRenewProcess: false, - jwtKeys: new JwtKeys(), + jwtKeys: null, validationResult: null, }; diff --git a/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts index 7dcbefefb..10a7398f9 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable max-len */ /* eslint-disable @typescript-eslint/naming-convention */ -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { BrowserModule } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; @@ -20,7 +20,7 @@ describe('TokenValidationService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [BrowserModule, HttpClientModule, RouterTestingModule, AuthModule.forRoot()], + imports: [BrowserModule, HttpClientTestingModule, RouterTestingModule, AuthModule.forRoot()], providers: [ ConfigurationProvider, EqualityService, diff --git a/projects/sample-code-flow-auto-login/src/app/app.component.ts b/projects/sample-code-flow-auto-login/src/app/app.component.ts index 73bcc8156..d734c8f3e 100644 --- a/projects/sample-code-flow-auto-login/src/app/app.component.ts +++ b/projects/sample-code-flow-auto-login/src/app/app.component.ts @@ -1,5 +1,4 @@ import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; import { OidcSecurityService } from 'angular-auth-oidc-client'; @Component({ @@ -8,24 +7,9 @@ import { OidcSecurityService } from 'angular-auth-oidc-client'; styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { - constructor(private oidcSecurityService: OidcSecurityService, private router: Router) {} + constructor(private oidcSecurityService: OidcSecurityService) {} - ngOnInit() { - this.oidcSecurityService - .checkAuth() - - .subscribe((isAuthenticated) => { - if (!isAuthenticated) { - if ('/autologin' !== window.location.pathname) { - this.write('redirect', window.location.pathname); - this.router.navigate(['/autologin']); - } - } - if (isAuthenticated) { - this.navigateToStoredEndpoint(); - } - }); - } + ngOnInit() {} login() { console.log('start login'); @@ -41,31 +25,4 @@ export class AppComponent implements OnInit { console.log('start logoff'); this.oidcSecurityService.logoff(); } - - private navigateToStoredEndpoint() { - const path = this.read('redirect'); - - if (this.router.url === path) { - return; - } - - if (path.toString().includes('/unauthorized')) { - this.router.navigate(['/']); - } else { - this.router.navigate([path]); - } - } - - private read(key: string): any { - const data = localStorage.getItem(key); - if (data) { - return JSON.parse(data); - } - - return; - } - - private write(key: string, value: any): void { - localStorage.setItem(key, JSON.stringify(value)); - } } diff --git a/projects/sample-code-flow-auto-login/src/app/app.module.ts b/projects/sample-code-flow-auto-login/src/app/app.module.ts index 3f416ea26..b5f233214 100644 --- a/projects/sample-code-flow-auto-login/src/app/app.module.ts +++ b/projects/sample-code-flow-auto-login/src/app/app.module.ts @@ -4,7 +4,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { routing } from './app.routes'; import { AuthConfigModule } from './auth-config.module'; -import { AutoLoginComponent } from './auto-login/auto-login.component'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { HomeComponent } from './home/home.component'; import { NavigationComponent } from './navigation/navigation.component'; @@ -12,7 +11,7 @@ import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; @NgModule({ imports: [BrowserModule, routing, HttpClientModule, AuthConfigModule], - declarations: [AppComponent, ForbiddenComponent, HomeComponent, AutoLoginComponent, NavigationComponent, UnauthorizedComponent], + declarations: [AppComponent, ForbiddenComponent, HomeComponent, NavigationComponent, UnauthorizedComponent], providers: [], bootstrap: [AppComponent], }) diff --git a/projects/sample-code-flow-auto-login/src/app/app.routes.ts b/projects/sample-code-flow-auto-login/src/app/app.routes.ts index 0de64e99a..2f7f76237 100644 --- a/projects/sample-code-flow-auto-login/src/app/app.routes.ts +++ b/projects/sample-code-flow-auto-login/src/app/app.routes.ts @@ -1,6 +1,5 @@ import { RouterModule, Routes } from '@angular/router'; -import { AuthorizationGuard } from './authorization.guard'; -import { AutoLoginComponent } from './auto-login/auto-login.component'; +import { AutoLoginGuard } from 'angular-auth-oidc-client'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { HomeComponent } from './home/home.component'; import { ProtectedComponent } from './protected/protected.component'; @@ -8,11 +7,10 @@ import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; const appRoutes: Routes = [ { path: '', pathMatch: 'full', redirectTo: 'home' }, - { path: 'home', component: HomeComponent }, - { path: 'autologin', component: AutoLoginComponent }, - { path: 'forbidden', component: ForbiddenComponent, canActivate: [AuthorizationGuard] }, + { path: 'home', component: HomeComponent, canActivate: [AutoLoginGuard] }, + { path: 'protected', component: ProtectedComponent, canActivate: [AutoLoginGuard] }, + { path: 'forbidden', component: ForbiddenComponent, canActivate: [AutoLoginGuard] }, { path: 'unauthorized', component: UnauthorizedComponent }, - { path: 'protected', component: ProtectedComponent }, ]; export const routing = RouterModule.forRoot(appRoutes); diff --git a/projects/sample-code-flow-auto-login/src/app/auth-config.module.ts b/projects/sample-code-flow-auto-login/src/app/auth-config.module.ts index 231dff759..5604a0834 100644 --- a/projects/sample-code-flow-auto-login/src/app/auth-config.module.ts +++ b/projects/sample-code-flow-auto-login/src/app/auth-config.module.ts @@ -1,8 +1,8 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AuthModule, LogLevel, OidcConfigService } from 'angular-auth-oidc-client'; -export function configureAuth(oidcConfigService: OidcConfigService) { - return () => +export const configureAuth = (oidcConfigService: OidcConfigService) => { + return () => { oidcConfigService.withConfig({ stsServer: 'https://offeringsolutions-sts.azurewebsites.net', redirectUrl: window.location.origin, @@ -22,7 +22,8 @@ export function configureAuth(oidcConfigService: OidcConfigService) { // iss_validation_off: false // disable_iat_offset_validation: true }); -} + }; +}; @NgModule({ imports: [AuthModule.forRoot()], diff --git a/projects/sample-code-flow-auto-login/src/app/authorization.guard.ts b/projects/sample-code-flow-auto-login/src/app/authorization.guard.ts deleted file mode 100644 index c1a06da4f..000000000 --- a/projects/sample-code-flow-auto-login/src/app/authorization.guard.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; -import { OidcSecurityService } from 'angular-auth-oidc-client'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -@Injectable({ providedIn: 'root' }) -export class AuthorizationGuard implements CanActivate { - constructor(private oidcSecurityService: OidcSecurityService, private router: Router) {} - - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - // return checkAuth() again should be possible - return this.oidcSecurityService.isAuthenticated$.pipe( - map((isAuthorized: boolean) => { - console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized); - - if (!isAuthorized) { - this.router.navigate(['/unauthorized']); - return false; - } - - return true; - }) - ); - } -} diff --git a/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.css b/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.html b/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.html deleted file mode 100644 index 795c64ded..000000000 --- a/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.html +++ /dev/null @@ -1 +0,0 @@ -
redirecting to login
diff --git a/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.spec.ts b/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.spec.ts deleted file mode 100644 index 9752eb14d..000000000 --- a/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { AutoLoginComponent } from './auto-login.component'; - -describe('AutoLoginComponent', () => { - let component: AutoLoginComponent; - let fixture: ComponentFixture; - - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [AutoLoginComponent], - }).compileComponents(); - }) - ); - - beforeEach(() => { - fixture = TestBed.createComponent(AutoLoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.ts b/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.ts deleted file mode 100644 index 0c05d8d93..000000000 --- a/projects/sample-code-flow-auto-login/src/app/auto-login/auto-login.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { OidcSecurityService } from 'angular-auth-oidc-client'; - -@Component({ - selector: 'app-auto-component', - templateUrl: './auto-login.component.html', -}) -export class AutoLoginComponent implements OnInit { - constructor(public oidcSecurityService: OidcSecurityService) {} - - ngOnInit() { - this.oidcSecurityService.authorize(); - } -} diff --git a/projects/sample-code-flow-auto-login/src/app/forbidden/forbidden.component.html b/projects/sample-code-flow-auto-login/src/app/forbidden/forbidden.component.html index 2c34bec60..78960e85d 100644 --- a/projects/sample-code-flow-auto-login/src/app/forbidden/forbidden.component.html +++ b/projects/sample-code-flow-auto-login/src/app/forbidden/forbidden.component.html @@ -1,5 +1 @@ -
- Normally you should not see this but your -
authenticated
- is {{ authenticated$ | async }} :-) -
+
Normally you should not see this, authenticated == {{ authenticated$ | async }}
diff --git a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.css b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.css index e83c8f87f..5707f312c 100644 --- a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.css +++ b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.css @@ -1,3 +1,3 @@ -li a { +.nav-link { cursor: pointer; } diff --git a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.html b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.html index 86dfe6888..0f87eab66 100644 --- a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.html +++ b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.html @@ -15,9 +15,11 @@ diff --git a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts index 8fab9d5ed..11d15995f 100644 --- a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts +++ b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts @@ -5,6 +5,7 @@ import { Observable } from 'rxjs'; @Component({ selector: 'app-navigation', templateUrl: 'navigation.component.html', + styleUrls: ['navigation.component.css'], }) export class NavigationComponent implements OnInit { isAuthenticated$: Observable; diff --git a/projects/sample-code-flow-azure-b2c/src/app/app.component.ts b/projects/sample-code-flow-azure-b2c/src/app/app.component.ts index 02d3f83a9..69c9a5b39 100644 --- a/projects/sample-code-flow-azure-b2c/src/app/app.component.ts +++ b/projects/sample-code-flow-azure-b2c/src/app/app.component.ts @@ -1,57 +1,10 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component } from '@angular/core'; import { OidcSecurityService } from 'angular-auth-oidc-client'; @Component({ selector: 'app-root', templateUrl: 'app.component.html', }) -export class AppComponent implements OnInit, OnDestroy { - constructor(public oidcSecurityService: OidcSecurityService, private router: Router) {} - - ngOnInit() { - this.oidcSecurityService - .checkAuth() - - .subscribe((isAuthenticated) => { - if (!isAuthenticated) { - if ('/autologin' !== window.location.pathname) { - this.write('redirect', window.location.pathname); - this.router.navigate(['/autologin']); - } - } - if (isAuthenticated) { - this.navigateToStoredEndpoint(); - } - }); - } - - ngOnDestroy(): void {} - - private navigateToStoredEndpoint() { - const path = this.read('redirect'); - - if (this.router.url === path) { - return; - } - - if (path.toString().includes('/unauthorized')) { - this.router.navigate(['/']); - } else { - this.router.navigate([path]); - } - } - - private read(key: string): any { - const data = localStorage.getItem(key); - if (data != null) { - return JSON.parse(data); - } - - return; - } - - private write(key: string, value: any): void { - localStorage.setItem(key, JSON.stringify(value)); - } +export class AppComponent { + constructor(public oidcSecurityService: OidcSecurityService) {} } diff --git a/projects/sample-code-flow-azure-b2c/src/app/app.module.ts b/projects/sample-code-flow-azure-b2c/src/app/app.module.ts index d1107ee6d..5f2faa3d9 100644 --- a/projects/sample-code-flow-azure-b2c/src/app/app.module.ts +++ b/projects/sample-code-flow-azure-b2c/src/app/app.module.ts @@ -5,8 +5,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { routing } from './app.routes'; import { AuthConfigModule } from './auth-config.module'; -import { AuthorizationGuard } from './authorization.guard'; -import { AutoLoginComponent } from './auto-login/auto-login.component'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { HomeComponent } from './home/home.component'; import { NavMenuComponent } from './nav-menu/nav-menu.component'; @@ -14,17 +12,9 @@ import { ProtectedComponent } from './protected/protected.component'; import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; @NgModule({ - declarations: [ - AppComponent, - NavMenuComponent, - HomeComponent, - AutoLoginComponent, - ForbiddenComponent, - UnauthorizedComponent, - ProtectedComponent, - ], + declarations: [AppComponent, NavMenuComponent, HomeComponent, ForbiddenComponent, UnauthorizedComponent, ProtectedComponent], imports: [BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), HttpClientModule, FormsModule, routing, AuthConfigModule], - providers: [AuthorizationGuard], + providers: [], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/projects/sample-code-flow-azure-b2c/src/app/app.routes.ts b/projects/sample-code-flow-azure-b2c/src/app/app.routes.ts index cb1d58df3..9be0c2b17 100644 --- a/projects/sample-code-flow-azure-b2c/src/app/app.routes.ts +++ b/projects/sample-code-flow-azure-b2c/src/app/app.routes.ts @@ -1,19 +1,16 @@ -import { Routes, RouterModule } from '@angular/router'; - +import { RouterModule, Routes } from '@angular/router'; +import { AutoLoginGuard } from 'angular-auth-oidc-client'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { HomeComponent } from './home/home.component'; -import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; -import { AutoLoginComponent } from './auto-login/auto-login.component'; import { ProtectedComponent } from './protected/protected.component'; -import { AuthorizationGuard } from './authorization.guard'; +import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; const appRoutes: Routes = [ - { path: '', component: HomeComponent, pathMatch: 'full' }, - { path: 'home', component: HomeComponent, canActivate: [AuthorizationGuard] }, - { path: 'autologin', component: AutoLoginComponent }, - { path: 'forbidden', component: ForbiddenComponent }, + { path: '', pathMatch: 'full', redirectTo: 'home' }, + { path: 'home', component: HomeComponent, canActivate: [AutoLoginGuard] }, + { path: 'forbidden', component: ForbiddenComponent, canActivate: [AutoLoginGuard] }, + { path: 'protected', component: ProtectedComponent, canActivate: [AutoLoginGuard] }, { path: 'unauthorized', component: UnauthorizedComponent }, - { path: 'protected', component: ProtectedComponent, canActivate: [AuthorizationGuard] }, ]; export const routing = RouterModule.forRoot(appRoutes); diff --git a/projects/sample-code-flow-azure-b2c/src/app/auth-config.module.ts b/projects/sample-code-flow-azure-b2c/src/app/auth-config.module.ts index 9e100eeb7..67d385791 100644 --- a/projects/sample-code-flow-azure-b2c/src/app/auth-config.module.ts +++ b/projects/sample-code-flow-azure-b2c/src/app/auth-config.module.ts @@ -20,6 +20,9 @@ export function loadConfig(oidcConfigService: OidcConfigService) { // useRefreshToken: true, // for refresh renew, but revocation and one time usage is missing from server impl. // ignoreNonceAfterRefresh: true, // disableRefreshIdTokenAuthTimeValidation: true, + customParams: { + prompt: 'select_account', // login, consent + }, }); } diff --git a/projects/sample-code-flow-azure-b2c/src/app/authorization.guard.ts b/projects/sample-code-flow-azure-b2c/src/app/authorization.guard.ts deleted file mode 100644 index 993f7fb4b..000000000 --- a/projects/sample-code-flow-azure-b2c/src/app/authorization.guard.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; -import { OidcSecurityService } from 'angular-auth-oidc-client'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -@Injectable({ providedIn: 'root' }) -export class AuthorizationGuard implements CanActivate { - constructor(private oidcSecurityService: OidcSecurityService, private router: Router) {} - - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.oidcSecurityService.isAuthenticated$.pipe( - map((isAuthorized: boolean) => { - console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized); - - if (!isAuthorized) { - this.router.navigate(['/unauthorized']); - return false; - } - - return true; - }) - ); - } -} diff --git a/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.css b/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.html b/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.html deleted file mode 100644 index 795c64ded..000000000 --- a/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.html +++ /dev/null @@ -1 +0,0 @@ -
redirecting to login
diff --git a/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.spec.ts b/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.spec.ts deleted file mode 100644 index 9752eb14d..000000000 --- a/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { AutoLoginComponent } from './auto-login.component'; - -describe('AutoLoginComponent', () => { - let component: AutoLoginComponent; - let fixture: ComponentFixture; - - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [AutoLoginComponent], - }).compileComponents(); - }) - ); - - beforeEach(() => { - fixture = TestBed.createComponent(AutoLoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.ts b/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.ts deleted file mode 100644 index ea8fa48d0..000000000 --- a/projects/sample-code-flow-azure-b2c/src/app/auto-login/auto-login.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { OidcSecurityService } from 'angular-auth-oidc-client'; - -@Component({ - selector: 'app-auto-component', - templateUrl: './auto-login.component.html', -}) -export class AutoLoginComponent implements OnInit { - lang: any; - - constructor(public oidcSecurityService: OidcSecurityService) {} - - ngOnInit() { - this.oidcSecurityService.checkAuth().subscribe(() => this.oidcSecurityService.authorize()); - } -} diff --git a/projects/sample-code-flow-azuread/src/app/app.component.ts b/projects/sample-code-flow-azuread/src/app/app.component.ts index 3d16a8f75..2a083f486 100644 --- a/projects/sample-code-flow-azuread/src/app/app.component.ts +++ b/projects/sample-code-flow-azuread/src/app/app.component.ts @@ -1,5 +1,4 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; import { OidcSecurityService } from 'angular-auth-oidc-client'; @Component({ @@ -7,24 +6,9 @@ import { OidcSecurityService } from 'angular-auth-oidc-client'; templateUrl: 'app.component.html', }) export class AppComponent implements OnInit, OnDestroy { - constructor(public oidcSecurityService: OidcSecurityService, private router: Router) {} + constructor(public oidcSecurityService: OidcSecurityService) {} - ngOnInit() { - this.oidcSecurityService - .checkAuth() - - .subscribe((isAuthenticated) => { - if (!isAuthenticated) { - if ('/autologin' !== window.location.pathname) { - this.write('redirect', window.location.pathname); - this.router.navigate(['/autologin']); - } - } - if (isAuthenticated) { - this.navigateToStoredEndpoint(); - } - }); - } + ngOnInit() {} ngOnDestroy(): void {} @@ -42,31 +26,4 @@ export class AppComponent implements OnInit, OnDestroy { console.log('start logoff'); this.oidcSecurityService.logoff(); } - - private navigateToStoredEndpoint() { - const path = this.read('redirect'); - - if (this.router.url === path) { - return; - } - - if (path.toString().includes('/unauthorized')) { - this.router.navigate(['/']); - } else { - this.router.navigate([path]); - } - } - - private read(key: string): any { - const data = localStorage.getItem(key); - if (data != null) { - return JSON.parse(data); - } - - return; - } - - private write(key: string, value: any): void { - localStorage.setItem(key, JSON.stringify(value)); - } } diff --git a/projects/sample-code-flow-azuread/src/app/app.module.ts b/projects/sample-code-flow-azuread/src/app/app.module.ts index d1107ee6d..d55ce6c8d 100644 --- a/projects/sample-code-flow-azuread/src/app/app.module.ts +++ b/projects/sample-code-flow-azuread/src/app/app.module.ts @@ -5,8 +5,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { routing } from './app.routes'; import { AuthConfigModule } from './auth-config.module'; -import { AuthorizationGuard } from './authorization.guard'; -import { AutoLoginComponent } from './auto-login/auto-login.component'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { HomeComponent } from './home/home.component'; import { NavMenuComponent } from './nav-menu/nav-menu.component'; @@ -14,17 +12,9 @@ import { ProtectedComponent } from './protected/protected.component'; import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; @NgModule({ - declarations: [ - AppComponent, - NavMenuComponent, - HomeComponent, - AutoLoginComponent, - ForbiddenComponent, - UnauthorizedComponent, - ProtectedComponent, - ], - imports: [BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), HttpClientModule, FormsModule, routing, AuthConfigModule], - providers: [AuthorizationGuard], + declarations: [AppComponent, NavMenuComponent, HomeComponent, ForbiddenComponent, UnauthorizedComponent, ProtectedComponent], + imports: [BrowserModule, HttpClientModule, FormsModule, routing, AuthConfigModule], + providers: [], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/projects/sample-code-flow-azuread/src/app/app.routes.ts b/projects/sample-code-flow-azuread/src/app/app.routes.ts index cb1d58df3..9be0c2b17 100644 --- a/projects/sample-code-flow-azuread/src/app/app.routes.ts +++ b/projects/sample-code-flow-azuread/src/app/app.routes.ts @@ -1,19 +1,16 @@ -import { Routes, RouterModule } from '@angular/router'; - +import { RouterModule, Routes } from '@angular/router'; +import { AutoLoginGuard } from 'angular-auth-oidc-client'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { HomeComponent } from './home/home.component'; -import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; -import { AutoLoginComponent } from './auto-login/auto-login.component'; import { ProtectedComponent } from './protected/protected.component'; -import { AuthorizationGuard } from './authorization.guard'; +import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; const appRoutes: Routes = [ - { path: '', component: HomeComponent, pathMatch: 'full' }, - { path: 'home', component: HomeComponent, canActivate: [AuthorizationGuard] }, - { path: 'autologin', component: AutoLoginComponent }, - { path: 'forbidden', component: ForbiddenComponent }, + { path: '', pathMatch: 'full', redirectTo: 'home' }, + { path: 'home', component: HomeComponent, canActivate: [AutoLoginGuard] }, + { path: 'forbidden', component: ForbiddenComponent, canActivate: [AutoLoginGuard] }, + { path: 'protected', component: ProtectedComponent, canActivate: [AutoLoginGuard] }, { path: 'unauthorized', component: UnauthorizedComponent }, - { path: 'protected', component: ProtectedComponent, canActivate: [AuthorizationGuard] }, ]; export const routing = RouterModule.forRoot(appRoutes); diff --git a/projects/sample-code-flow-azuread/src/app/auth-config.module.ts b/projects/sample-code-flow-azuread/src/app/auth-config.module.ts index 85f0c6ca3..fe452bba5 100644 --- a/projects/sample-code-flow-azuread/src/app/auth-config.module.ts +++ b/projects/sample-code-flow-azuread/src/app/auth-config.module.ts @@ -8,16 +8,17 @@ export function loadConfig(oidcConfigService: OidcConfigService) { authWellknownEndpoint: 'https://login.microsoftonline.com/common/v2.0', redirectUrl: window.location.origin, clientId: 'e38ea64a-2962-4cde-bfe7-dd2822fdab32', - scope: 'openid profile email api://e38ea64a-2962-4cde-bfe7-dd2822fdab32/access_as_user', + scope: 'openid profile offline_access email api://e38ea64a-2962-4cde-bfe7-dd2822fdab32/access_as_user', responseType: 'code', silentRenew: true, maxIdTokenIatOffsetAllowedInSeconds: 600, issValidationOff: true, autoUserinfo: false, - silentRenewUrl: window.location.origin + '/silent-renew.html', + // silentRenewUrl: window.location.origin + '/silent-renew.html', + useRefreshToken: true, logLevel: LogLevel.Debug, customParams: { - prompt: 'consent', // login, consent + prompt: 'select_account', // login, consent }, }); } diff --git a/projects/sample-code-flow-azuread/src/app/authorization.guard.ts b/projects/sample-code-flow-azuread/src/app/authorization.guard.ts deleted file mode 100644 index 993f7fb4b..000000000 --- a/projects/sample-code-flow-azuread/src/app/authorization.guard.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; -import { OidcSecurityService } from 'angular-auth-oidc-client'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -@Injectable({ providedIn: 'root' }) -export class AuthorizationGuard implements CanActivate { - constructor(private oidcSecurityService: OidcSecurityService, private router: Router) {} - - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.oidcSecurityService.isAuthenticated$.pipe( - map((isAuthorized: boolean) => { - console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized); - - if (!isAuthorized) { - this.router.navigate(['/unauthorized']); - return false; - } - - return true; - }) - ); - } -} diff --git a/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.css b/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.html b/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.html deleted file mode 100644 index 795c64ded..000000000 --- a/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.html +++ /dev/null @@ -1 +0,0 @@ -
redirecting to login
diff --git a/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.spec.ts b/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.spec.ts deleted file mode 100644 index 9752eb14d..000000000 --- a/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { AutoLoginComponent } from './auto-login.component'; - -describe('AutoLoginComponent', () => { - let component: AutoLoginComponent; - let fixture: ComponentFixture; - - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [AutoLoginComponent], - }).compileComponents(); - }) - ); - - beforeEach(() => { - fixture = TestBed.createComponent(AutoLoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.ts b/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.ts deleted file mode 100644 index ea8fa48d0..000000000 --- a/projects/sample-code-flow-azuread/src/app/auto-login/auto-login.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { OidcSecurityService } from 'angular-auth-oidc-client'; - -@Component({ - selector: 'app-auto-component', - templateUrl: './auto-login.component.html', -}) -export class AutoLoginComponent implements OnInit { - lang: any; - - constructor(public oidcSecurityService: OidcSecurityService) {} - - ngOnInit() { - this.oidcSecurityService.checkAuth().subscribe(() => this.oidcSecurityService.authorize()); - } -} diff --git a/projects/schematics/package.json b/projects/schematics/package.json index 46d7628fd..e17ce32e9 100644 --- a/projects/schematics/package.json +++ b/projects/schematics/package.json @@ -1,6 +1,6 @@ { "name": "schematics", - "version": "11.6.0", + "version": "11.6.1", "description": "A schematic for the Angular Lib for OpenID Connect & OAuth2", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/projects/schematics/src/ng-add/actions/add-dependencies.ts b/projects/schematics/src/ng-add/actions/add-dependencies.ts index d39567fb3..06b5399df 100644 --- a/projects/schematics/src/ng-add/actions/add-dependencies.ts +++ b/projects/schematics/src/ng-add/actions/add-dependencies.ts @@ -4,7 +4,7 @@ import { addPackageJsonDependency, NodeDependency, NodeDependencyType } from '@s const dependenciesToAdd = [ { name: 'angular-auth-oidc-client', - version: '11.6.0', + version: '11.6.1', }, ]; diff --git a/projects/schematics/src/ng-add/actions/configs.ts b/projects/schematics/src/ng-add/actions/configs.ts index b54087a82..e79339672 100644 --- a/projects/schematics/src/ng-add/actions/configs.ts +++ b/projects/schematics/src/ng-add/actions/configs.ts @@ -34,6 +34,9 @@ const AZURE_AD_SILENT_RENEW = `{ issValidationOff: false, autoUserinfo: false, silentRenewUrl: window.location.origin + '/silent-renew.html', + customParams: { + prompt: 'select_account', // login, consent + }, }`; const AZURE_AD_REFRESH_TOKENS = `{ @@ -48,6 +51,9 @@ const AZURE_AD_REFRESH_TOKENS = `{ maxIdTokenIatOffsetAllowedInSeconds: 600, issValidationOff: false, autoUserinfo: false, + customParams: { + prompt: 'select_account', // login, consent + }, }`; const OAUTH_PAR = `{