diff --git a/README.md b/README.md index 215d7927..63bafc4b 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Please note, that this dependency is not needed for the **code flow**, which is ### Breaking change in 9.1.0 The use of `encodeURIComponent` on the argument passed to `initImplicitFlow` and its Code Flow counterparts was mandatory before this version. + Since that was considered a _bug_, the need to do so was removed. Now the reverse is true **if you're upgrading from before 9.0.0**: you need to remove any call to encode URI components in your own application, as the library will now do it for you. @@ -48,7 +49,7 @@ Now the reverse is true **if you're upgrading from before 9.0.0**: you need to r Successfully tested with **Angular 9** and its Router, PathLocationStrategy as well as HashLocationStrategy and CommonJS-Bundling via webpack. At server side we've used IdentityServer (.NET / .NET Core) and Redhat's Keycloak (Java). -**Angular 9**: Use 9.x versions of this library (should also work with older Angular versions!). +**Angular 9**: Use 9.x versions of this library (**should also work with older Angular versions!**). **Angular 8**: Use 8.x versions of this library. @@ -90,6 +91,7 @@ Successfully tested with **Angular 9** and its Router, PathLocationStrategy as w - Hook for further custom validations - Single-Sign-Out by redirecting to the auth-server's logout-endpoint - Tested with all modern browsers and IE +- Token Revocation according to [RFC 7009](https://tools.ietf.org/html/rfc7009#section-2.2) ## Sample-Auth-Server @@ -204,6 +206,20 @@ this.oauthService.configure(authCodeFlowConfig); this.oauthService.loadDiscoveryDocumentAndTryLogin(); ``` +### Logging out + +The logOut method clears the used token store (by default ``sessionStorage``) and forwards the user to the auth servers logout endpoint if one was configured (manually or via the discovery document). + +```typescript +this.oauthService.logOut(); +``` + +If you want to revoke the existing access token and the existing refresh token before logging out, use the following method: + +```typescript +this.oauthService.revokeTokenAndLogout(); +``` + ### Skipping the Login Form If you don't want to display a login form that tells the user that they are redirected to the identity server, you can use the convenience function `this.oauthService.loadDiscoveryDocumentAndLogin();` instead of `this.oauthService.loadDiscoveryDocumentAndTryLogin();` when setting up the library. diff --git a/projects/lib/src/oauth-service.ts b/projects/lib/src/oauth-service.ts index f3139b0d..5a031cd7 100644 --- a/projects/lib/src/oauth-service.ts +++ b/projects/lib/src/oauth-service.ts @@ -1,6 +1,6 @@ import { Injectable, NgZone, Optional, OnDestroy, Inject } from '@angular/core'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Observable, Subject, Subscription, of, race, from } from 'rxjs'; +import { Observable, Subject, Subscription, of, race, from, combineLatest } from 'rxjs'; import { filter, delay, @@ -2557,11 +2557,15 @@ export class OAuthService extends AuthConfig implements OnDestroy { * up any security credentials associated with the authorization */ public revokeTokenAndLogout(): Promise { - let revoke_endpoint = this.revocationEndpoint; - let current_access_token = this.getAccessToken(); - let params = new HttpParams() - .set('token', current_access_token) - .set('token_type_hint', 'access_token'); + let revokeEndpoint = this.revocationEndpoint; + let accessToken = this.getAccessToken(); + let refreshToken = this.getRefreshToken(); + + if (!accessToken) { + return; + } + + let params = new HttpParams(); let headers = new HttpHeaders().set( 'Content-Type', @@ -2588,10 +2592,29 @@ export class OAuthService extends AuthConfig implements OnDestroy { } return new Promise((resolve, reject) => { - if (current_access_token) { - this.http - .post(revoke_endpoint, params, { headers }) - .subscribe( + let revokeAccessToken: Observable; + let revokeRefreshToken: Observable; + + if (accessToken) { + let revokationParams = params + .set('token', accessToken) + .set('token_type_hint', 'access_token'); + revokeAccessToken = this.http.post(revokeEndpoint, revokationParams, { headers }); + } else { + revokeAccessToken = of(null); + } + + if (refreshToken) { + let revokationParams = params + .set('token', refreshToken) + .set('token_type_hint', 'refresh_token'); + revokeRefreshToken = this.http.post(revokeEndpoint, revokationParams, { headers }); + } else { + revokeRefreshToken = of(null); + } + + combineLatest([revokeAccessToken, revokeRefreshToken]) + .subscribe( res => { this.logOut(); resolve(res); @@ -2605,9 +2628,6 @@ export class OAuthService extends AuthConfig implements OnDestroy { reject(err); } ); - } else { - this.logger.warn('User not logged in to revoke token.'); - } }); } } diff --git a/projects/sample/src/app/app.component.ts b/projects/sample/src/app/app.component.ts index b7694944..0f88f872 100644 --- a/projects/sample/src/app/app.component.ts +++ b/projects/sample/src/app/app.component.ts @@ -33,10 +33,16 @@ export class AppComponent { private configureCodeFlow() { this.oauthService.configure(authCodeFlowConfig); - this.oauthService.loadDiscoveryDocumentAndTryLogin(); + this.oauthService.loadDiscoveryDocumentAndTryLogin().then(_ => { + if (useHash) { + this.router.navigate(['/']); + } + }); // Optional - // this.oauthService.setupAutomaticSilentRefresh(); + this.oauthService.setupAutomaticSilentRefresh(); + + } private configureImplicitFlow() { diff --git a/projects/sample/src/app/auth-code-flow.config.ts b/projects/sample/src/app/auth-code-flow.config.ts index 78ad262c..2c25e138 100644 --- a/projects/sample/src/app/auth-code-flow.config.ts +++ b/projects/sample/src/app/auth-code-flow.config.ts @@ -46,6 +46,8 @@ export const authCodeFlowConfig: AuthConfig = { sessionChecksEnabled: true, - timeoutFactor: 0.01 + timeoutFactor: 0.01, // disablePKCI: true, + + clearHashAfterLogin: false, }; diff --git a/projects/sample/src/app/home/home.component.ts b/projects/sample/src/app/home/home.component.ts index 5c69c2c1..2de0f919 100644 --- a/projects/sample/src/app/home/home.component.ts +++ b/projects/sample/src/app/home/home.component.ts @@ -79,7 +79,8 @@ export class HomeComponent implements OnInit { } logout() { - this.oauthService.logOut(); + // this.oauthService.logOut(); + this.oauthService.revokeTokenAndLogout(); } loadUserProfile(): void { diff --git a/projects/sample/src/flags.ts b/projects/sample/src/flags.ts index 5aea8cd0..100788c3 100644 --- a/projects/sample/src/flags.ts +++ b/projects/sample/src/flags.ts @@ -1,6 +1,6 @@ // Use HashLocationStrategy for routing? -export const useHash = false; +export const useHash = true; // Set this to true, to use silent refresh; otherwise the example // uses the refresh_token via an AJAX coll to get new tokens. -export const useSilentRefreshForCodeFlow = true; +export const useSilentRefreshForCodeFlow = false;