From fcd6e4ff08c8c00c49d23dd96253d0fb659609f4 Mon Sep 17 00:00:00 2001 From: Severin Skillman Date: Fri, 16 Oct 2020 22:56:20 +0100 Subject: [PATCH 1/8] added idTokenClaims$ observable to AuthService with tests --- README.md | 8 +++++ .../src/lib/auth.service.spec.ts | 31 ++++++++++++++++++- .../auth0-angular/src/lib/auth.service.ts | 9 ++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5aa326ba..4345b336 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,14 @@ Access the `user$` observable on the `AuthService` instance to retrieve the user ``` +### Access ID token claims + +Access the `idTokenClaims$` observable on the `AuthService` instance to retrieve the ID token claims. Like the `user$` observable, this observable already heeds the `isAuthenticated$` observable, so you do not need to check if the user is authenticated before using it: + +```js +authService.idTokenClaims$.subscribe((claims) => console.log(claims)); +``` + ### Handle errors Errors in the login flow can be captured by subscribing to the `error$` observable: diff --git a/projects/auth0-angular/src/lib/auth.service.spec.ts b/projects/auth0-angular/src/lib/auth.service.spec.ts index 520bb68b..4f55b209 100644 --- a/projects/auth0-angular/src/lib/auth.service.spec.ts +++ b/projects/auth0-angular/src/lib/auth.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed } from '@angular/core/testing'; import { AuthService } from './auth.service'; import { Auth0ClientService } from './auth.client'; -import { Auth0Client } from '@auth0/auth0-spa-js'; +import { Auth0Client, IdToken } from '@auth0/auth0-spa-js'; import { AbstractNavigator } from './abstract-navigator'; import { filter } from 'rxjs/operators'; import { Location } from '@angular/common'; @@ -36,6 +36,7 @@ describe('AuthService', () => { spyOn(auth0Client, 'checkSession').and.resolveTo(); spyOn(auth0Client, 'isAuthenticated').and.resolveTo(false); spyOn(auth0Client, 'getUser').and.resolveTo(null); + spyOn(auth0Client, 'getIdTokenClaims').and.resolveTo(null); spyOn(auth0Client, 'logout'); spyOn(auth0Client, 'getTokenSilently').and.resolveTo('__access_token__'); @@ -136,6 +137,34 @@ describe('AuthService', () => { }); }); + describe('The `idTokenClaims` observable', () => { + it('should get the ID token claims if authenticated', (done) => { + const claims: IdToken = { + __raw: 'idToken', + exp: 1602887231, + iat: 1602883631, + iss: 'https://example.eu.auth0.com/', + }; + + (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(true); + (auth0Client.getIdTokenClaims as jasmine.Spy).and.resolveTo(claims); + + service.idTokenClaims$.subscribe((value) => { + expect(value).toBe(claims); + done(); + }); + }); + + it('should get the ID token claims if not authenticated', (done) => { + (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(true); + + service.idTokenClaims$.subscribe((value) => { + expect(value).toBeFalsy(); + done(); + }); + }); + }); + describe('when handling the redirect callback', () => { let navigator: AbstractNavigator; diff --git a/projects/auth0-angular/src/lib/auth.service.ts b/projects/auth0-angular/src/lib/auth.service.ts index 9011da63..fcdcdeea 100644 --- a/projects/auth0-angular/src/lib/auth.service.ts +++ b/projects/auth0-angular/src/lib/auth.service.ts @@ -72,6 +72,15 @@ export class AuthService implements OnDestroy { concatMap(() => this.auth0Client.getUser()) ); + /** + * Emits ID token claims when `isAuthenticated$` is `true`. + */ + readonly idTokenClaims$ = this.isAuthenticated$.pipe( + filter((authenticated) => authenticated), + distinctUntilChanged(), + concatMap(() => this.auth0Client.getIdTokenClaims()) + ); + /** * Emits errors that occur during login, or when checking for an active session on startup. */ From db4b36414b0ad8717bf1cfec0ab51c2e0831b9d2 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Tue, 20 Oct 2020 16:35:29 +0100 Subject: [PATCH 2/8] Add username & password for test user --- projects/playground/e2e/integration/playground.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/playground/e2e/integration/playground.spec.ts b/projects/playground/e2e/integration/playground.spec.ts index 32517311..05c7b782 100644 --- a/projects/playground/e2e/integration/playground.spec.ts +++ b/projects/playground/e2e/integration/playground.spec.ts @@ -1,5 +1,5 @@ -const EMAIL = Cypress.env('USER_EMAIL'); -const PASSWORD = Cypress.env('USER_PASSWORD'); +const EMAIL = 'johnfoo+integration@gmail.com'; +const PASSWORD = '1234'; if (!EMAIL || !PASSWORD) { throw new Error( From a567e138007f9177e9a34ac0f87281e5a1b33f72 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Tue, 20 Oct 2020 17:18:23 +0100 Subject: [PATCH 3/8] Remove check on username and password --- projects/playground/e2e/integration/playground.spec.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/projects/playground/e2e/integration/playground.spec.ts b/projects/playground/e2e/integration/playground.spec.ts index 05c7b782..c4dec909 100644 --- a/projects/playground/e2e/integration/playground.spec.ts +++ b/projects/playground/e2e/integration/playground.spec.ts @@ -1,12 +1,6 @@ const EMAIL = 'johnfoo+integration@gmail.com'; const PASSWORD = '1234'; -if (!EMAIL || !PASSWORD) { - throw new Error( - 'You must provide CYPRESS_USER_EMAIL and CYPRESS_USER_PASSWORD environment variables' - ); -} - const loginToAuth0 = () => { cy.get('.auth0-lock-form') .should('have.length.above', 1) From e9e3904e1a550dea401f91a2d7610e689d8191a2 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Tue, 20 Oct 2020 17:22:27 +0100 Subject: [PATCH 4/8] Add playground artifact for id token claims --- projects/playground/src/app/app.component.html | 13 +++++++++++++ projects/playground/src/app/app.component.ts | 1 + 2 files changed, 14 insertions(+) diff --git a/projects/playground/src/app/app.component.html b/projects/playground/src/app/app.component.html index 81f25a13..fb503c95 100644 --- a/projects/playground/src/app/app.component.html +++ b/projects/playground/src/app/app.component.html @@ -72,6 +72,19 @@

Artifacts

user$ | async | json }} +
  • +

    ID token claims:

    + +
  • Access Token: Select a mode and click the button to retrieve the diff --git a/projects/playground/src/app/app.component.ts b/projects/playground/src/app/app.component.ts index 0a7c564b..9a719ec2 100644 --- a/projects/playground/src/app/app.component.ts +++ b/projects/playground/src/app/app.component.ts @@ -15,6 +15,7 @@ export class AppComponent { isAuthenticated$ = this.auth.isAuthenticated$; isLoading$ = this.auth.isLoading$; user$ = this.auth.user$; + claims$ = this.auth.idTokenClaims$; accessToken = ''; error$ = this.auth.error$; From 318ec68458e686a25207a613c133b5e4877df49d Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Tue, 20 Oct 2020 17:25:29 +0100 Subject: [PATCH 5/8] Add check for id token claims into e2e test --- projects/playground/e2e/integration/playground.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/playground/e2e/integration/playground.spec.ts b/projects/playground/e2e/integration/playground.spec.ts index c4dec909..f6841b1c 100644 --- a/projects/playground/e2e/integration/playground.spec.ts +++ b/projects/playground/e2e/integration/playground.spec.ts @@ -45,14 +45,13 @@ describe('Smoke tests', () => { it('do redirect login and show user and access token', () => { cy.visit('/'); cy.get('#login').should('be.visible').click(); - cy.url().should('include', 'https://brucke.auth0.com/login'); loginToAuth0(); - cy.get('[data-cy=userProfile]').contains(`"email": "${EMAIL}"`); - + cy.get('[data-cy=idTokenClaims]').contains('__raw'); cy.get('[data-cy=accessToken]').should('be.empty'); cy.get('#accessToken').click(); + cy.get('[data-cy=accessToken]') .should('not.be.empty') .invoke('text') From f1a317a0f18129ba2d9a6bcf133ba5d7bbbc84c8 Mon Sep 17 00:00:00 2001 From: Severin Skillman Date: Wed, 21 Oct 2020 10:03:44 +0100 Subject: [PATCH 6/8] better negative tests for user$ and idTokenClaims$ --- .../src/lib/auth.service.spec.ts | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/projects/auth0-angular/src/lib/auth.service.spec.ts b/projects/auth0-angular/src/lib/auth.service.spec.ts index 4f55b209..2200f2d9 100644 --- a/projects/auth0-angular/src/lib/auth.service.spec.ts +++ b/projects/auth0-angular/src/lib/auth.service.spec.ts @@ -127,13 +127,18 @@ describe('AuthService', () => { }); }); - it('should get the user if not authenticated', (done) => { - (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(true); + it('should not get the user if not authenticated', (done) => { + (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(false); + let user; service.user$.subscribe((value) => { - expect(value).toBeFalsy(); - done(); + user = value; }); + + setTimeout(() => { + expect(user).toBeUndefined(); + done(); + }, 1000); }); }); @@ -155,13 +160,18 @@ describe('AuthService', () => { }); }); - it('should get the ID token claims if not authenticated', (done) => { - (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(true); + it('should not get the ID token claims if not authenticated', (done) => { + (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(false); + let claims; service.idTokenClaims$.subscribe((value) => { - expect(value).toBeFalsy(); - done(); + claims = value; }); + + setTimeout(() => { + expect(claims).toBeUndefined(); + done(); + }, 1000); }); }); From 5bc0760ae404dd58143010ab59e98ac4660e2da1 Mon Sep 17 00:00:00 2001 From: Severin Skillman Date: Wed, 21 Oct 2020 10:10:56 +0100 Subject: [PATCH 7/8] better negative tests for user$ and idTokenClaims$ --- .../src/lib/auth.service.spec.ts | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/projects/auth0-angular/src/lib/auth.service.spec.ts b/projects/auth0-angular/src/lib/auth.service.spec.ts index 2200f2d9..68a7f0bd 100644 --- a/projects/auth0-angular/src/lib/auth.service.spec.ts +++ b/projects/auth0-angular/src/lib/auth.service.spec.ts @@ -128,15 +128,20 @@ describe('AuthService', () => { }); it('should not get the user if not authenticated', (done) => { + const user = { + name: 'Test User', + }; + (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(false); + (auth0Client.getUser as jasmine.Spy).and.resolveTo(user); - let user; - service.user$.subscribe((value) => { - user = value; + let value; + service.user$.subscribe((v) => { + value = v; }); setTimeout(() => { - expect(user).toBeUndefined(); + expect(value).toBeUndefined(); done(); }, 1000); }); @@ -161,15 +166,23 @@ describe('AuthService', () => { }); it('should not get the ID token claims if not authenticated', (done) => { + const claims: IdToken = { + __raw: 'idToken', + exp: 1602887231, + iat: 1602883631, + iss: 'https://example.eu.auth0.com/', + }; + (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(false); + (auth0Client.getIdTokenClaims as jasmine.Spy).and.resolveTo(claims); - let claims; - service.idTokenClaims$.subscribe((value) => { - claims = value; + let value; + service.idTokenClaims$.subscribe((v) => { + value = v; }); setTimeout(() => { - expect(claims).toBeUndefined(); + expect(value).toBeUndefined(); done(); }, 1000); }); From 8e0ff67d5a975db5264edbbdcd96737fb5a5707b Mon Sep 17 00:00:00 2001 From: Severin Skillman Date: Thu, 22 Oct 2020 12:28:24 +0100 Subject: [PATCH 8/8] removed redundant tests for user$ and idTokenClaims$ --- .../src/lib/auth.service.spec.ts | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/projects/auth0-angular/src/lib/auth.service.spec.ts b/projects/auth0-angular/src/lib/auth.service.spec.ts index 68a7f0bd..b35ec06e 100644 --- a/projects/auth0-angular/src/lib/auth.service.spec.ts +++ b/projects/auth0-angular/src/lib/auth.service.spec.ts @@ -126,25 +126,6 @@ describe('AuthService', () => { done(); }); }); - - it('should not get the user if not authenticated', (done) => { - const user = { - name: 'Test User', - }; - - (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(false); - (auth0Client.getUser as jasmine.Spy).and.resolveTo(user); - - let value; - service.user$.subscribe((v) => { - value = v; - }); - - setTimeout(() => { - expect(value).toBeUndefined(); - done(); - }, 1000); - }); }); describe('The `idTokenClaims` observable', () => { @@ -164,28 +145,6 @@ describe('AuthService', () => { done(); }); }); - - it('should not get the ID token claims if not authenticated', (done) => { - const claims: IdToken = { - __raw: 'idToken', - exp: 1602887231, - iat: 1602883631, - iss: 'https://example.eu.auth0.com/', - }; - - (auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(false); - (auth0Client.getIdTokenClaims as jasmine.Spy).and.resolveTo(claims); - - let value; - service.idTokenClaims$.subscribe((v) => { - value = v; - }); - - setTimeout(() => { - expect(value).toBeUndefined(); - done(); - }, 1000); - }); }); describe('when handling the redirect callback', () => {