From e6061cb40950b00362e4fcd6563bcd282b2ca4e4 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Thu, 27 Feb 2020 15:36:10 +0000 Subject: [PATCH 1/3] Logic falls back to the iframe method when no refresh token is found --- __tests__/index.test.ts | 37 +++++++++++++++++-------------------- src/Auth0Client.ts | 5 +---- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index 47021f9f8..2521be0ff 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -1442,26 +1442,6 @@ describe('Auth0', () => { } }); }); - - it('fails with an error when no refresh token is available in the cache', async () => { - const { auth0, cache, utils } = await setup({ - useRefreshTokens: true - }); - - utils.getUniqueScopes.mockReturnValue( - `${TEST_SCOPES} offline_access` - ); - - cache.get.mockReturnValue({ access_token: TEST_ACCESS_TOKEN }); - - await auth0.getTokenSilently({ ignoreCache: true }).catch(e => { - expect(e.error).toBe('missing_refresh_token'); - expect(e.error_description).toBe( - 'No refresh token is available to fetch a new access token. The user should be reauthenticated.' - ); - expect(utils.oauthToken).not.toHaveBeenCalled(); - }); - }); }); }); @@ -1736,6 +1716,23 @@ describe('Auth0', () => { ); }); }); + + describe('when refresh tokens are used', () => { + it('falls back to using a hidden iframe when no refresh token is available', async () => { + const { auth0, cache, utils } = await setup({ + useRefreshTokens: true + }); + + utils.getUniqueScopes.mockReturnValue(`${TEST_SCOPES} offline_access`); + + cache.get.mockReturnValue({ access_token: TEST_ACCESS_TOKEN }); + + const result = await auth0.getTokenSilently({ ignoreCache: true }); + + expect(result).toEqual(TEST_ACCESS_TOKEN); + expect(utils.runIframe).toHaveBeenCalled(); + }); + }); }); describe('getTokenWithPopup()', async () => { diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts index 14e8a250c..f070bb7ec 100644 --- a/src/Auth0Client.ts +++ b/src/Auth0Client.ts @@ -625,10 +625,7 @@ export default class Auth0Client { }); if (!cache || !cache.refresh_token) { - throw new GenericError( - 'missing_refresh_token', - 'No refresh token is available to fetch a new access token. The user should be reauthenticated.' - ); + return await this._getTokenFromIFrame(options); } const redirect_uri = From 120fc34f53cc09d7c55b72e0ceeeb1ccac7cd0b5 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Thu, 27 Feb 2020 15:38:36 +0000 Subject: [PATCH 2/3] Cleaned up a variable name --- src/Auth0Client.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts index f070bb7ec..ac5860931 100644 --- a/src/Auth0Client.ts +++ b/src/Auth0Client.ts @@ -439,7 +439,7 @@ export default class Auth0Client { * @param options */ public async getTokenSilently(options: GetTokenSilentlyOptions = {}) { - const { ignoreCache, ...refreshTokenOptions } = { + const { ignoreCache, ...getTokenOptions } = { audience: this.options.audience, scope: getUniqueScopes( this.DEFAULT_SCOPE, @@ -453,8 +453,8 @@ export default class Auth0Client { try { if (!ignoreCache) { const cache = this.cache.get({ - scope: refreshTokenOptions.scope, - audience: refreshTokenOptions.audience || 'default', + scope: getTokenOptions.scope, + audience: getTokenOptions.audience || 'default', client_id: this.options.client_id }); @@ -466,8 +466,8 @@ export default class Auth0Client { await lock.acquireLock(GET_TOKEN_SILENTLY_LOCK_KEY, 5000); const authResult = this.options.useRefreshTokens - ? await this._getTokenUsingRefreshToken(refreshTokenOptions) - : await this._getTokenFromIFrame(refreshTokenOptions); + ? await this._getTokenUsingRefreshToken(getTokenOptions) + : await this._getTokenFromIFrame(getTokenOptions); this.cache.save({ client_id: this.options.client_id, ...authResult }); From c19154069d1fe6b994fab61628a6c4153d5a5af8 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Thu, 27 Feb 2020 16:10:12 +0000 Subject: [PATCH 3/3] Updated integration test --- cypress/integration/getTokenSilently.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/cypress/integration/getTokenSilently.js b/cypress/integration/getTokenSilently.js index 63733889f..272449c39 100644 --- a/cypress/integration/getTokenSilently.js +++ b/cypress/integration/getTokenSilently.js @@ -1,11 +1,4 @@ -import { decode } from 'qss'; -import { - shouldBe, - shouldNotBe, - shouldBeUndefined, - shouldNotBeUndefined, - whenReady -} from '../support/utils'; +import { shouldBe, whenReady } from '../support/utils'; describe('getTokenSilently', function() { beforeEach(cy.resetTests); @@ -76,7 +69,11 @@ describe('getTokenSilently', function() { }); describe('when using refresh tokens', () => { - it('displays an error when trying to get an access token when the RT is missing', () => { + /** + * This test will fail with a 'consent_required' error when running on localhost, but the fact that it does + * proves that the iframe method was attempted even though we're supposed to be using refresh tokens. + */ + it.only('attempts to retrieve an access token by falling back to the iframe method', () => { return whenReady().then(win => { cy.toggleSwitch('local-storage'); cy.toggleSwitch('use-cache'); @@ -87,12 +84,11 @@ describe('getTokenSilently', function() { cy.get('[data-cy=get-token]') .click() - .wait(500); + .wait(500) + .get('[data-cy=access-token]') + .should('have.length', 1); - cy.get('[data-cy=error]').should( - 'contain', - 'No refresh token is available to fetch a new access token' - ); + cy.get('[data-cy=error]').should('contain', 'consent_required'); }); }); });