From b0541c8aad103ec1cbe5194f5a7ff86076ad7258 Mon Sep 17 00:00:00 2001 From: Ewan Harris Date: Thu, 18 Aug 2022 16:47:41 +0100 Subject: [PATCH] [SDK-3548] Introduce authorizationParams to hold properties sent to Auth0 (#959) --- README.md | 52 ++-- __tests__/Auth0Client/buildLogoutUrl.test.ts | 13 +- __tests__/Auth0Client/constructor.test.ts | 15 +- .../Auth0Client/getIdTokenClaims.test.ts | 19 +- .../Auth0Client/getTokenSilently.test.ts | 114 ++++++--- .../Auth0Client/getTokenWithPopup.test.ts | 20 +- .../handleRedirectCallback.test.ts | 4 +- __tests__/Auth0Client/helpers.ts | 25 +- __tests__/Auth0Client/loginWithPopup.test.ts | 83 ++++-- .../Auth0Client/loginWithRedirect.test.ts | 43 +++- __tests__/Auth0Client/logout.test.ts | 5 +- __tests__/index.test.ts | 6 +- src/Auth0Client.ts | 239 +++++++----------- src/global.ts | 182 ++++++------- src/utils.ts | 4 +- static/index.html | 60 +++-- static/multiple_clients.html | 4 +- 17 files changed, 510 insertions(+), 378 deletions(-) diff --git a/README.md b/README.md index 1c528b354..efa5c099a 100644 --- a/README.md +++ b/README.md @@ -81,14 +81,18 @@ import { createAuth0Client } from '@auth0/auth0-spa-js'; const auth0 = await createAuth0Client({ domain: '', clientId: '', - redirect_uri: '' + authorizationParams: { + redirect_uri: '' + } }); //with promises createAuth0Client({ domain: '', clientId: '', - redirect_uri: '' + authorizationParams: { + redirect_uri: '' + } }).then(auth0 => { //... }); @@ -99,7 +103,9 @@ import { Auth0Client } from '@auth0/auth0-spa-js'; const auth0 = new Auth0Client({ domain: '', clientId: '', - redirect_uri: '' + authorizationParams: { + redirect_uri: '' + } }); //if you do this, you'll need to check the session yourself @@ -224,9 +230,11 @@ To use the in-memory mode, no additional options need are required as this is th ```js await createAuth0Client({ domain: '', - clientId: '', - redirect_uri: '', - cacheLocation: 'localstorage' // valid values are: 'memory' or 'localstorage' + clientId: '',, + cacheLocation: 'localstorage' // valid values are: 'memory' or 'localstorage', + authorizationParams: { + redirect_uri: '' + } }); ``` @@ -272,8 +280,10 @@ const sessionStorageCache = { await createAuth0Client({ domain: '', clientId: '', - redirect_uri: '', - cache: sessionStorageCache + cache: sessionStorageCache, + authorizationParams: { + redirect_uri: '' + } }); ``` @@ -291,8 +301,10 @@ To enable the use of refresh tokens, set the `useRefreshTokens` option to `true` await createAuth0Client({ domain: '', clientId: '', - redirect_uri: '', - useRefreshTokens: true + useRefreshTokens: true, + authorizationParams: { + redirect_uri: '' + } }); ``` @@ -320,8 +332,10 @@ Log in to an organization by specifying the `organization` parameter when settin createAuth0Client({ domain: '', clientId: '', - redirect_uri: '', - organization: '' + authorizationParams: { + organization: '', + redirect_uri: '' + } }); ``` @@ -330,12 +344,16 @@ You can also specify the organization when logging in: ```js // Using a redirect client.loginWithRedirect({ - organization: '' + authorizationParams: { + organization: '' + } }); // Using a popup window client.loginWithPopup({ - organization: '' + authorizationParams: { + organization: '' + } }); ``` @@ -351,8 +369,10 @@ const invitation = params.get('invitation'); if (organization && invitation) { client.loginWithRedirect({ - organization, - invitation + authorizationParams: { + invitation, + organization + } }); } ``` diff --git a/__tests__/Auth0Client/buildLogoutUrl.test.ts b/__tests__/Auth0Client/buildLogoutUrl.test.ts index 07a1732ed..d55fe1d9e 100644 --- a/__tests__/Auth0Client/buildLogoutUrl.test.ts +++ b/__tests__/Auth0Client/buildLogoutUrl.test.ts @@ -98,8 +98,10 @@ describe('Auth0Client', () => { const auth0 = setup(); const url = auth0.buildLogoutUrl({ - returnTo: 'https://return.to', - clientId: null + clientId: null, + logoutParams: { + returnTo: 'https://return.to' + } }); assertUrlEquals(url, TEST_DOMAIN, '/v2/logout', { @@ -110,7 +112,12 @@ describe('Auth0Client', () => { it('creates correct query params when `options.federated` is true', async () => { const auth0 = setup(); - const url = auth0.buildLogoutUrl({ federated: true, clientId: null }); + const url = auth0.buildLogoutUrl({ + logoutParams: { + federated: true + }, + clientId: null + }); assertUrlEquals(url, TEST_DOMAIN, '/v2/logout', { federated: '' diff --git a/__tests__/Auth0Client/constructor.test.ts b/__tests__/Auth0Client/constructor.test.ts index 419714003..ac6b3e269 100644 --- a/__tests__/Auth0Client/constructor.test.ts +++ b/__tests__/Auth0Client/constructor.test.ts @@ -77,7 +77,9 @@ describe('Auth0Client', () => { it('automatically adds the offline_access scope during construction', () => { const auth0 = setup({ useRefreshTokens: true, - scope: 'test-scope' + authorizationParams: { + scope: 'test-scope' + } }); expect((auth0).scope).toBe('test-scope offline_access'); @@ -197,7 +199,9 @@ describe('Auth0Client', () => { const auth0 = setup(); const url = auth0.buildLogoutUrl({ - returnTo: 'https://return.to', + logoutParams: { + returnTo: 'https://return.to' + }, clientId: null }); @@ -209,7 +213,12 @@ describe('Auth0Client', () => { it('creates correct query params when `options.federated` is true', async () => { const auth0 = setup(); - const url = auth0.buildLogoutUrl({ federated: true, clientId: null }); + const url = auth0.buildLogoutUrl({ + logoutParams: { + federated: true + }, + clientId: null + }); assertUrlEquals(url, TEST_DOMAIN, '/v2/logout', { federated: '' diff --git a/__tests__/Auth0Client/getIdTokenClaims.test.ts b/__tests__/Auth0Client/getIdTokenClaims.test.ts index d0b87427c..70caf43e5 100644 --- a/__tests__/Auth0Client/getIdTokenClaims.test.ts +++ b/__tests__/Auth0Client/getIdTokenClaims.test.ts @@ -102,7 +102,7 @@ describe('Auth0Client', () => { }) => { describe(`when ${name}`, () => { it('returns the ID token claims', async () => { - const auth0 = setup({ scope: 'foo' }); + const auth0 = setup({ authorizationParams: { scope: 'foo' } }); await login(auth0); expect(await auth0.getIdTokenClaims()).toHaveProperty('exp'); @@ -121,12 +121,14 @@ describe('Auth0Client', () => { it('returns the ID token claims with custom scope', async () => { const auth0 = setup({ - scope: 'scope1', + authorizationParams: { + scope: 'scope1' + }, advancedOptions: { defaultScope: 'scope2' } }); - await login(auth0, { scope: 'scope3' }); + await login(auth0, { authorizationParams: { scope: 'scope3' } }); expect( await auth0.getIdTokenClaims({ scope: 'scope1 scope2 scope3' }) @@ -135,7 +137,10 @@ describe('Auth0Client', () => { describe('when using refresh tokens', () => { it('returns the ID token claims with offline_access', async () => { - const auth0 = setup({ scope: 'foo', useRefreshTokens: true }); + const auth0 = setup({ + authorizationParams: { scope: 'foo' }, + useRefreshTokens: true + }); await login(auth0); expect( @@ -145,13 +150,15 @@ describe('Auth0Client', () => { it('returns the ID token claims with custom scope and offline_access', async () => { const auth0 = setup({ - scope: 'scope1', + authorizationParams: { + scope: 'scope1' + }, advancedOptions: { defaultScope: 'scope2' }, useRefreshTokens: true }); - await login(auth0, { scope: 'scope3' }); + await login(auth0, { authorizationParams: { scope: 'scope3' } }); expect( await auth0.getIdTokenClaims({ diff --git a/__tests__/Auth0Client/getTokenSilently.test.ts b/__tests__/Auth0Client/getTokenSilently.test.ts index 5ca5f1560..a8948d216 100644 --- a/__tests__/Auth0Client/getTokenSilently.test.ts +++ b/__tests__/Auth0Client/getTokenSilently.test.ts @@ -139,7 +139,9 @@ describe('Auth0Client', () => { }); await getTokenSilently(auth0, { - foo: 'bar' + authorizationParams: { + foo: 'bar' + } }); const [[url]] = (utils.runIframe).mock.calls; @@ -162,7 +164,9 @@ describe('Auth0Client', () => { it('calls the authorize endpoint using the correct params when using a default redirect_uri', async () => { const redirect_uri = 'https://custom-redirect-uri/callback'; const auth0 = setup({ - redirect_uri + authorizationParams: { + redirect_uri + } }); jest.spyOn(utils, 'runIframe').mockResolvedValue({ @@ -313,7 +317,9 @@ describe('Auth0Client', () => { mockFetch.mockReset(); await getTokenSilently(auth0, { - redirect_uri, + authorizationParams: { + redirect_uri + }, cacheMode: 'off' }); @@ -343,7 +349,9 @@ describe('Auth0Client', () => { mockFetch.mockReset(); await getTokenSilently(auth0, { - redirect_uri, + authorizationParams: { + redirect_uri + }, cacheMode: 'off' }); @@ -366,8 +374,10 @@ describe('Auth0Client', () => { it('calls the token endpoint with the correct params when not providing any redirect uri, using refresh tokens and not using useFormData', async () => { const auth0 = setup({ useRefreshTokens: true, - redirect_uri: null, - useFormData: false + useFormData: false, + authorizationParams: { + redirect_uri: null + } }); await loginWithRedirect(auth0); @@ -375,7 +385,9 @@ describe('Auth0Client', () => { mockFetch.mockReset(); await getTokenSilently(auth0, { - redirect_uri: null, + authorizationParams: { + redirect_uri: null + }, cacheMode: 'off' }); @@ -396,7 +408,9 @@ describe('Auth0Client', () => { it('calls the token endpoint with the correct params when not providing any redirect uri and using refresh tokens', async () => { const auth0 = setup({ useRefreshTokens: true, - redirect_uri: null + authorizationParams: { + redirect_uri: null + } }); await loginWithRedirect(auth0); @@ -404,7 +418,9 @@ describe('Auth0Client', () => { mockFetch.mockReset(); await getTokenSilently(auth0, { - redirect_uri: null, + authorizationParams: { + redirect_uri: null + }, cacheMode: 'off' }); @@ -1375,15 +1391,19 @@ describe('Auth0Client', () => { }) ); let access_token = await auth0.getTokenSilently({ - audience: 'foo', - scope: 'bar' + authorizationParams: { + audience: 'foo', + scope: 'bar' + } }); expect(access_token).toEqual(TEST_ACCESS_TOKEN); expect(utils.runIframe).toHaveBeenCalledTimes(1); (utils.runIframe).mockClear(); access_token = await auth0.getTokenSilently({ - audience: 'foo', - scope: 'bar' + authorizationParams: { + audience: 'foo', + scope: 'bar' + } }); expect(access_token).toEqual(TEST_ACCESS_TOKEN); expect(utils.runIframe).not.toHaveBeenCalled(); @@ -1404,12 +1424,16 @@ describe('Auth0Client', () => { expires_in: 86400 }) ); - let access_token = await auth0.getTokenSilently({ audience: 'foo' }); + let access_token = await auth0.getTokenSilently({ + authorizationParams: { audience: 'foo' } + }); expect(access_token).toEqual(TEST_ACCESS_TOKEN); expect(acquireLockSpy).toHaveBeenCalled(); acquireLockSpy.mockClear(); // This request will hit the cache, so should not acquire the lock - access_token = await auth0.getTokenSilently({ audience: 'foo' }); + access_token = await auth0.getTokenSilently({ + authorizationParams: { audience: 'foo' } + }); expect(access_token).toEqual(TEST_ACCESS_TOKEN); expect(acquireLockSpy).not.toHaveBeenCalled(); }); @@ -1502,8 +1526,10 @@ describe('Auth0Client', () => { it('sends custom options through to the token endpoint when using an iframe when not using useFormData', async () => { const auth0 = setup({ - custom_param: 'foo', - another_custom_param: 'bar', + authorizationParams: { + custom_param: 'foo', + another_custom_param: 'bar' + }, useFormData: false }); @@ -1525,7 +1551,9 @@ describe('Auth0Client', () => { await auth0.getTokenSilently({ cacheMode: 'off', - custom_param: 'hello world' + authorizationParams: { + custom_param: 'hello world' + } }); expect( @@ -1546,8 +1574,10 @@ describe('Auth0Client', () => { it('sends custom options through to the token endpoint when using an iframe', async () => { const auth0 = setup({ - custom_param: 'foo', - another_custom_param: 'bar' + authorizationParams: { + custom_param: 'foo', + another_custom_param: 'bar' + } }); await loginWithRedirect(auth0); @@ -1568,7 +1598,9 @@ describe('Auth0Client', () => { await auth0.getTokenSilently({ cacheMode: 'off', - custom_param: 'hello world' + authorizationParams: { + custom_param: 'hello world' + } }); expect( @@ -1597,9 +1629,11 @@ describe('Auth0Client', () => { it('sends custom options through to the token endpoint when using refresh tokens when not using useFormData', async () => { const auth0 = setup({ + authorizationParams: { + custom_param: 'foo', + another_custom_param: 'bar' + }, useRefreshTokens: true, - custom_param: 'foo', - another_custom_param: 'bar', useFormData: false }); @@ -1627,7 +1661,9 @@ describe('Auth0Client', () => { const access_token = await auth0.getTokenSilently({ cacheMode: 'off', - custom_param: 'hello world' + authorizationParams: { + custom_param: 'hello world' + } }); expect(JSON.parse(mockFetch.mock.calls[1][1].body)).toEqual({ @@ -1645,8 +1681,10 @@ describe('Auth0Client', () => { it('sends custom options through to the token endpoint when using refresh tokens', async () => { const auth0 = setup({ useRefreshTokens: true, - custom_param: 'foo', - another_custom_param: 'bar' + authorizationParams: { + custom_param: 'foo', + another_custom_param: 'bar' + } }); await loginWithRedirect(auth0, undefined, { @@ -1673,7 +1711,9 @@ describe('Auth0Client', () => { const access_token = await auth0.getTokenSilently({ cacheMode: 'off', - custom_param: 'helloworld' + authorizationParams: { + custom_param: 'helloworld' + } }); assertPost( @@ -1872,7 +1912,9 @@ describe('Auth0Client', () => { it('opens iframe with correct urls including organization from the options', async () => { const auth0 = setup({ authorizeTimeoutInSeconds: 1, - organization: TEST_ORG_ID + authorizationParams: { + organization: TEST_ORG_ID + } }); jest.spyOn(utils, 'runIframe').mockResolvedValue({ @@ -1915,7 +1957,9 @@ describe('Auth0Client', () => { it('opens iframe with correct urls including organization, with options taking precedence over hint cookie', async () => { const auth0 = setup({ authorizeTimeoutInSeconds: 1, - organization: 'another_test_org' + authorizationParams: { + organization: 'another_test_org' + } }); jest.spyOn(utils, 'runIframe').mockResolvedValue({ @@ -2180,7 +2224,9 @@ describe('Auth0Client', () => { it('returns the full response with scopes when "detailedResponse: true" and using cache', async () => { const auth0 = setup({ - scope: 'read:messages write:messages' + authorizationParams: { + scope: 'read:messages write:messages' + } }); const runIframeSpy = jest @@ -2206,7 +2252,9 @@ describe('Auth0Client', () => { await auth0.getTokenSilently({ cacheMode: 'off', - scope: 'read:messages' + authorizationParams: { + scope: 'read:messages' + } }); expect(auth0['cacheManager'].set).toHaveBeenCalledWith( @@ -2224,7 +2272,9 @@ describe('Auth0Client', () => { // oauthTokenScope in the scope property const response = await auth0.getTokenSilently({ detailedResponse: true, - scope: 'read:messages' + authorizationParams: { + scope: 'read:messages' + } }); // No refresh_token included here, or oauthTokenScope diff --git a/__tests__/Auth0Client/getTokenWithPopup.test.ts b/__tests__/Auth0Client/getTokenWithPopup.test.ts index 46564306d..8aed4dbad 100644 --- a/__tests__/Auth0Client/getTokenWithPopup.test.ts +++ b/__tests__/Auth0Client/getTokenWithPopup.test.ts @@ -113,7 +113,9 @@ describe('Auth0Client', () => { advancedOptions: { defaultScope: 'email' }, - scope: 'read:email' + authorizationParams: { + scope: 'read:email' + } }); const config = { @@ -136,8 +138,10 @@ describe('Auth0Client', () => { const auth0 = await localSetup(); const loginOptions = { - audience: 'other-audience', - screen_hint: 'signup' + authorizationParams: { + audience: 'other-audience', + screen_hint: 'signup' + } }; const config = { @@ -159,8 +163,10 @@ describe('Auth0Client', () => { const auth0 = await localSetup({}); const loginOptions = { - audience: 'other-audience', - screen_hint: 'signup' + authorizationParams: { + audience: 'other-audience', + screen_hint: 'signup' + } }; const config = { @@ -193,7 +199,9 @@ describe('Auth0Client', () => { it('can use the global audience', async () => { const auth0 = await localSetup({ - audience: 'global-audience' + authorizationParams: { + audience: 'global-audience' + } }); const config = { diff --git a/__tests__/Auth0Client/handleRedirectCallback.test.ts b/__tests__/Auth0Client/handleRedirectCallback.test.ts index 190a81def..bb2c756e1 100644 --- a/__tests__/Auth0Client/handleRedirectCallback.test.ts +++ b/__tests__/Auth0Client/handleRedirectCallback.test.ts @@ -329,7 +329,7 @@ describe('Auth0Client', () => { const auth0 = setup({ useFormData: false }); - delete auth0['options']['redirect_uri']; + delete auth0['options']['authorizationParams']?.['redirect_uri']; await loginWithRedirect(auth0); @@ -356,7 +356,7 @@ describe('Auth0Client', () => { ); const auth0 = setup(); - delete auth0['options']['redirect_uri']; + delete auth0['options']['authorizationParams']?.['redirect_uri']; await loginWithRedirect(auth0); diff --git a/__tests__/Auth0Client/helpers.ts b/__tests__/Auth0Client/helpers.ts index 669240eb2..eb997a1f4 100644 --- a/__tests__/Auth0Client/helpers.ts +++ b/__tests__/Auth0Client/helpers.ts @@ -106,16 +106,21 @@ export const fetchResponse = (ok, json) => export const setupFn = (mockVerify: jest.Mock) => { return (config?: Partial, claims?: Partial) => { - const auth0 = new Auth0Client( - Object.assign( - { - domain: TEST_DOMAIN, - clientId: TEST_CLIENT_ID, - redirect_uri: TEST_REDIRECT_URI - }, - config - ) - ); + const options: Auth0ClientOptions = { + domain: TEST_DOMAIN, + clientId: TEST_CLIENT_ID, + authorizationParams: { + redirect_uri: TEST_REDIRECT_URI + } + }; + + Object.assign(options.authorizationParams, config?.authorizationParams); + + delete config?.authorizationParams; + + Object.assign(options, config); + + const auth0 = new Auth0Client(options); mockVerify.mockReturnValue({ claims: Object.assign( diff --git a/__tests__/Auth0Client/loginWithPopup.test.ts b/__tests__/Auth0Client/loginWithPopup.test.ts index c1c157f8a..4a65589f3 100644 --- a/__tests__/Auth0Client/loginWithPopup.test.ts +++ b/__tests__/Auth0Client/loginWithPopup.test.ts @@ -99,7 +99,7 @@ describe('Auth0Client', () => { describe('loginWithPopup', () => { it('should log the user in and get the user and claims', async () => { - const auth0 = setup({ scope: 'foo' }); + const auth0 = setup({ authorizationParams: { scope: 'foo' } }); mockWindow.open.mockReturnValue({ hello: 'world' }); @@ -127,12 +127,14 @@ describe('Auth0Client', () => { it('should log the user in with custom scope', async () => { const auth0 = setup({ - scope: 'scope1', + authorizationParams: { + scope: 'scope1' + }, advancedOptions: { defaultScope: 'scope2' } }); - await loginWithPopup(auth0, { scope: 'scope3' }); + await loginWithPopup(auth0, { authorizationParams: { scope: 'scope3' } }); const expectedUser = { sub: 'me' }; @@ -165,8 +167,10 @@ describe('Auth0Client', () => { const auth0 = setup({ leeway: 10, httpTimeoutInSeconds: 60 }); await loginWithPopup(auth0, { - connection: 'test-connection', - audience: 'test' + authorizationParams: { + connection: 'test-connection', + audience: 'test' + } }); expect(mockWindow.open).toHaveBeenCalled(); @@ -212,11 +216,16 @@ describe('Auth0Client', () => { }); it('should log the user in with a popup and redirect using a default redirect URI', async () => { - const auth0 = setup({ leeway: 10, redirect_uri: null }); + const auth0 = setup({ + leeway: 10, + authorizationParams: { redirect_uri: undefined } + }); await loginWithPopup(auth0, { - connection: 'test-connection', - audience: 'test' + authorizationParams: { + connection: 'test-connection', + audience: 'test' + } }); expect(mockWindow.open).toHaveBeenCalled(); @@ -243,8 +252,10 @@ describe('Auth0Client', () => { const auth0 = setup({ leeway: 10 }); await loginWithPopup(auth0, { - connection: 'test-connection', - audience: 'test' + authorizationParams: { + connection: 'test-connection', + audience: 'test' + } }); expect(mockWindow.open).toHaveBeenCalled(); @@ -291,7 +302,9 @@ describe('Auth0Client', () => { it('should log the user and redirect when using different default redirect_uri', async () => { const redirect_uri = 'https://custom-redirect-uri/callback'; const auth0 = setup({ - redirect_uri + authorizationParams: { + redirect_uri + } }); await loginWithPopup(auth0); @@ -406,7 +419,12 @@ describe('Auth0Client', () => { await loginWithPopup( auth0, - { connection: 'test-connection', audience: 'test' }, + { + authorizationParams: { + connection: 'test-connection', + audience: 'test' + } + }, { popup } ); @@ -515,7 +533,7 @@ describe('Auth0Client', () => { }); it('calls `tokenVerifier.verify` with undefined `max_age` when value set in constructor is an empty string', async () => { - const auth0 = setup({ max_age: '' }); + const auth0 = setup({ authorizationParams: { max_age: '' } }); await loginWithPopup(auth0); @@ -527,7 +545,7 @@ describe('Auth0Client', () => { }); it('calls `tokenVerifier.verify` with the parsed `max_age` string from constructor', async () => { - const auth0 = setup({ max_age: '10' }); + const auth0 = setup({ authorizationParams: { max_age: '10' } }); await loginWithPopup(auth0); @@ -539,7 +557,7 @@ describe('Auth0Client', () => { }); it('calls `tokenVerifier.verify` with the parsed `max_age` number from constructor', async () => { - const auth0 = setup({ max_age: 10 }); + const auth0 = setup({ authorizationParams: { max_age: 10 } }); await loginWithPopup(auth0); @@ -551,7 +569,9 @@ describe('Auth0Client', () => { }); it('calls `tokenVerifier.verify` with the organization id', async () => { - const auth0 = setup({ organization: 'test_org_123' }); + const auth0 = setup({ + authorizationParams: { organization: 'test_org_123' } + }); await loginWithPopup(auth0); @@ -564,7 +584,9 @@ describe('Auth0Client', () => { it('calls `tokenVerifier.verify` with the organization id given in the login method', async () => { const auth0 = setup(); - await loginWithPopup(auth0, { organization: 'test_org_123' }); + await loginWithPopup(auth0, { + authorizationParams: { organization: 'test_org_123' } + }); expect(tokenVerifier).toHaveBeenCalledWith( expect.objectContaining({ @@ -740,5 +762,32 @@ describe('Auth0Client', () => { 'HTTP error. Unable to fetch https://auth0_domain/oauth/token' ); }); + + it('should log the user and redirect when using different redirect_uri on loginWithPopup', async () => { + const redirect_uri = 'https://custom-redirect-uri/callback'; + const auth0 = setup({ + authorizationParams: { + redirect_uri: 'https://redirect-uri-on-ctor/callback' + } + }); + await loginWithPopup(auth0, { + authorizationParams: { + redirect_uri + } + }); + + // prettier-ignore + const url = (utils.runPopup as jest.Mock).mock.calls[0][0].popup.location.href; + + assertUrlEquals( + url, + TEST_DOMAIN, + '/authorize', + { + redirect_uri + }, + false + ); + }); }); }); diff --git a/__tests__/Auth0Client/loginWithRedirect.test.ts b/__tests__/Auth0Client/loginWithRedirect.test.ts index 6e7c0fd92..b97c8f995 100644 --- a/__tests__/Auth0Client/loginWithRedirect.test.ts +++ b/__tests__/Auth0Client/loginWithRedirect.test.ts @@ -208,7 +208,9 @@ describe('Auth0Client', () => { const redirect_uri = 'https://custom-redirect-uri/callback'; const auth0 = setup({ - redirect_uri + authorizationParams: { + redirect_uri + } }); await loginWithRedirect(auth0); @@ -230,11 +232,15 @@ describe('Auth0Client', () => { const redirect_uri = 'https://custom-redirect-uri/callback'; const auth0 = setup({ - redirect_uri + authorizationParams: { + redirect_uri + } }); await loginWithRedirect(auth0, { - redirect_uri: 'https://my-redirect-uri/callback' + authorizationParams: { + redirect_uri: 'https://my-redirect-uri/callback' + } }); const url = new URL(mockWindow.location.assign.mock.calls[0][0]); @@ -254,7 +260,9 @@ describe('Auth0Client', () => { const auth0 = setup(); await loginWithRedirect(auth0, { - audience: 'test_audience', + authorizationParams: { + audience: 'test_audience' + }, onRedirect: async url => window.location.replace(url) }); @@ -275,7 +283,9 @@ describe('Auth0Client', () => { const auth0 = setup(); await loginWithRedirect(auth0, { - audience: 'test_audience' + authorizationParams: { + audience: 'test_audience' + } }); const url = new URL(mockWindow.location.assign.mock.calls[0][0]); @@ -312,7 +322,7 @@ describe('Auth0Client', () => { }); it('should log the user in and get the user', async () => { - const auth0 = setup({ scope: 'foo' }); + const auth0 = setup({ authorizationParams: { scope: 'foo' } }); await loginWithRedirect(auth0); const expectedUser = { sub: 'me' }; @@ -328,13 +338,17 @@ describe('Auth0Client', () => { it('should log the user in and get the user with custom scope', async () => { const auth0 = setup({ - scope: 'scope1', + authorizationParams: { + scope: 'scope1' + }, advancedOptions: { defaultScope: 'scope2' } }); - await loginWithRedirect(auth0, { scope: 'scope3' }); + await loginWithRedirect(auth0, { + authorizationParams: { scope: 'scope3' } + }); const expectedUser = { sub: 'me' }; @@ -412,7 +426,9 @@ describe('Auth0Client', () => { }); it('calls `tokenVerifier.verify` with the global organization id', async () => { - const auth0 = setup({ organization: 'test_org_123' }); + const auth0 = setup({ + authorizationParams: { organization: 'test_org_123' } + }); await loginWithRedirect(auth0); @@ -460,10 +476,13 @@ describe('Auth0Client', () => { }); it('calls `tokenVerifier.verify` with the specific organization id', async () => { - const auth0 = setup({ organization: 'test_org_123' }); - - await loginWithRedirect(auth0, { organization: 'test_org_456' }); + const auth0 = setup({ + authorizationParams: { organization: 'test_org_123' } + }); + await loginWithRedirect(auth0, { + authorizationParams: { organization: 'test_org_456' } + }); expect(tokenVerifier).toHaveBeenCalledWith( expect.objectContaining({ organizationId: 'test_org_456' diff --git a/__tests__/Auth0Client/logout.test.ts b/__tests__/Auth0Client/logout.test.ts index ed1af34c5..44ce379e7 100644 --- a/__tests__/Auth0Client/logout.test.ts +++ b/__tests__/Auth0Client/logout.test.ts @@ -101,7 +101,7 @@ describe('Auth0Client', () => { it('calls `window.location.assign` with the correct url when `options.federated` is true', async () => { const auth0 = setup(); - auth0.logout({ federated: true }); + auth0.logout({ logoutParams: { federated: true } }); expect(window.location.assign).toHaveBeenCalledWith( `https://${TEST_DOMAIN}/v2/logout?client_id=${TEST_CLIENT_ID}${TEST_AUTH0_CLIENT_QUERY_STRING}&federated` @@ -170,7 +170,8 @@ describe('Auth0Client', () => { it('throws when both `options.localOnly` and `options.federated` are true', async () => { const auth0 = setup(); - const fn = () => auth0.logout({ localOnly: true, federated: true }); + const fn = () => + auth0.logout({ localOnly: true, logoutParams: { federated: true } }); expect(fn).toThrow(); }); diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index 81aefc268..22c8dbd09 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -280,8 +280,10 @@ describe('Auth0', () => { const { cookieStorage } = await setup(null, false); const options = { - audience: 'the-audience', - scope: 'the-scope', + authorizationParams: { + audience: 'the-audience', + scope: 'the-scope' + }, useRefreshTokens: true }; diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts index cfeb9d0eb..f51266757 100644 --- a/src/Auth0Client.ts +++ b/src/Auth0Client.ts @@ -56,7 +56,7 @@ import { import { Auth0ClientOptions, - BaseLoginOptions, + AuthorizationParams, AuthorizeOptions, RedirectLoginOptions, PopupLoginOptions, @@ -156,43 +156,12 @@ const getDomain = (domainUrl: string) => { return domainUrl; }; -/** - * @ignore - */ -const getCustomInitialOptions = ( - options: Auth0ClientOptions -): BaseLoginOptions => { - const { - advancedOptions, - audience, - auth0Client, - authorizeTimeoutInSeconds, - cacheLocation, - cache, - clientId, - domain, - issuer, - leeway, - max_age, - nowProvider, - redirect_uri, - scope, - useRefreshTokens, - useRefreshTokensFallback, - useCookiesForTransactions, - useFormData, - ...customParams - } = options; - return customParams; -}; - /** * Auth0 SDK for Single Page Applications using [Authorization Code Grant Flow with PKCE](https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce). */ export class Auth0Client { private readonly transactionManager: TransactionManager; private readonly cacheManager: CacheManager; - private readonly customOptions: BaseLoginOptions; private readonly domainUrl: string; private readonly tokenIssuer: string; private readonly defaultScope: string; @@ -209,6 +178,7 @@ export class Auth0Client { private worker: Worker; private readonly defaultOptions: Partial = { + authorizationParams: {}, useRefreshTokensFallback: false, useFormData: true }; @@ -262,7 +232,7 @@ export class Auth0Client { ? this.cookieStorage : SessionStorage; - this.scope = this.options.scope; + this.scope = this.options.authorizationParams?.scope; this.transactionManager = new TransactionManager( transactionStorage, @@ -284,7 +254,7 @@ export class Auth0Client { this.defaultScope = getUniqueScopes( 'openid', - this.options?.advancedOptions?.defaultScope !== undefined + this.options.advancedOptions?.defaultScope !== undefined ? this.options.advancedOptions.defaultScope : DEFAULT_SCOPE ); @@ -305,8 +275,6 @@ export class Auth0Client { ) { this.worker = new TokenWorker(); } - - this.customOptions = getCustomInitialOptions(options); } private _url(path: string) { @@ -317,47 +285,27 @@ export class Auth0Client { } private _getParams( - authorizeOptions: BaseLoginOptions, + authorizeOptions: AuthorizationParams, state: string, nonce: string, code_challenge: string, redirect_uri: string ): AuthorizeOptions { - // These options should be excluded from the authorize URL, - // as they're options for the client and not for the IdP. - // ** IMPORTANT ** If adding a new client option, include it in this destructure list. - const { - useRefreshTokens, - useCookiesForTransactions, - useFormData, - auth0Client, - cacheLocation, - advancedOptions, - detailedResponse, - nowProvider, - authorizeTimeoutInSeconds, - legacySameSiteCookie, - sessionCheckExpiryDays, - domain, - leeway, - httpTimeoutInSeconds, - useRefreshTokensFallback, - ...loginOptions - } = this.options; - return { - ...loginOptions, + client_id: this.options.clientId, + ...this.options.authorizationParams, ...authorizeOptions, scope: getUniqueScopes( this.defaultScope, this.scope, - authorizeOptions.scope + authorizeOptions?.scope ), response_type: 'code', response_mode: 'query', state, nonce, - redirect_uri: redirect_uri || this.options.redirect_uri, + redirect_uri: + redirect_uri || this.options.authorizationParams?.redirect_uri, code_challenge, code_challenge_method: 'S256' }; @@ -381,7 +329,7 @@ export class Auth0Client { nonce, organizationId, leeway: this.options.leeway, - max_age: this._parseNumber(this.options.max_age), + max_age: this._parseNumber(this.options.authorizationParams?.max_age), now }); } @@ -423,9 +371,7 @@ export class Auth0Client { return url; } - private async _prepareAuthorizeUrl( - options: RedirectLoginOptions = {} - ): Promise<{ + private async _prepareAuthorizeUrl(options: RedirectLoginOptions): Promise<{ scope: string; audience: string; redirect_uri: string; @@ -435,7 +381,7 @@ export class Auth0Client { state: string; url: string; }> { - const { redirect_uri, appState, ...authorizeOptions } = options; + const { appState, authorizationParams } = options; const state = encode(createRandomString()); const nonce = encode(createRandomString()); @@ -445,11 +391,11 @@ export class Auth0Client { const fragment = options.fragment ? `#${options.fragment}` : ''; const params = this._getParams( - authorizeOptions, + authorizationParams, state, nonce, code_challenge, - redirect_uri + authorizationParams?.redirect_uri ); const url = this._authorizeUrl(params); @@ -506,7 +452,6 @@ export class Auth0Client { } } - const { ...authorizeOptions } = options; const stateIn = encode(createRandomString()); const nonceIn = encode(createRandomString()); const code_verifier = createRandomString(); @@ -514,11 +459,13 @@ export class Auth0Client { const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer); const params = this._getParams( - authorizeOptions, + options.authorizationParams, stateIn, nonceIn, code_challenge, - this.options.redirect_uri || window.location.origin + options.authorizationParams?.redirect_uri || + this.options.authorizationParams?.redirect_uri || + window.location.origin ); const url = this._authorizeUrl({ @@ -557,7 +504,9 @@ export class Auth0Client { this.worker ); - const organizationId = options.organization || this.options.organization; + const organizationId = + options.authorizationParams?.organization || + this.options.authorizationParams?.organization; const decodedToken = await this._verifyIdToken( authResult.id_token, @@ -601,7 +550,10 @@ export class Auth0Client { public async getUser( options: GetUserOptions = {} ): Promise { - const audience = options.audience || this.options.audience || 'default'; + const audience = + options.audience || + this.options.authorizationParams?.audience || + 'default'; const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope); const cache = await this.cacheManager.get( @@ -631,7 +583,10 @@ export class Auth0Client { public async getIdTokenClaims( options: GetIdTokenClaimsOptions = {} ): Promise { - const audience = options.audience || this.options.audience || 'default'; + const audience = + options.audience || + this.options.authorizationParams?.audience || + 'default'; const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope); const cache = await this.cacheManager.get( @@ -661,7 +616,9 @@ export class Auth0Client { ) { const { onRedirect, ...urlOptions } = options; - const organizationId = urlOptions.organization || this.options.organization; + const organizationId = + urlOptions.authorizationParams?.organization || + this.options.authorizationParams?.organization; const { url, ...transaction } = await this._prepareAuthorizeUrl(urlOptions); @@ -870,25 +827,28 @@ export class Auth0Client { public async getTokenSilently( options: GetTokenSilentlyOptions = {} ): Promise { - const { cacheMode, ...getTokenOptions }: GetTokenSilentlyOptions = { - audience: this.options.audience, + options = { cacheMode: 'on', ...options, - scope: getUniqueScopes(this.defaultScope, this.scope, options.scope) + authorizationParams: { + ...this.options.authorizationParams, + ...options.authorizationParams, + scope: getUniqueScopes( + this.defaultScope, + this.scope, + options.authorizationParams?.scope + ) + } }; return singlePromise( - () => - this._getTokenSilently({ - cacheMode, - ...getTokenOptions - }), - `${this.options.clientId}::${getTokenOptions.audience}::${getTokenOptions.scope}` + () => this._getTokenSilently(options), + `${this.options.clientId}::${options.authorizationParams?.audience}::${options.authorizationParams?.scope}` ); } private async _getTokenSilently( - options: GetTokenSilentlyOptions = {} + options: GetTokenSilentlyOptions ): Promise { const { cacheMode, ...getTokenOptions } = options; @@ -896,8 +856,8 @@ export class Auth0Client { // `lock.acquireLock` when the cache is populated. if (cacheMode !== 'off') { const entry = await this._getEntryFromCache({ - scope: getTokenOptions.scope, - audience: getTokenOptions.audience || 'default', + scope: getTokenOptions.authorizationParams?.scope, + audience: getTokenOptions.authorizationParams?.audience || 'default', clientId: this.options.clientId, getDetailedEntry: options.detailedResponse }); @@ -922,8 +882,8 @@ export class Auth0Client { // by a previous call while this call was waiting to acquire the lock. if (cacheMode !== 'off') { const entry = await this._getEntryFromCache({ - scope: getTokenOptions.scope, - audience: getTokenOptions.audience || 'default', + scope: getTokenOptions.authorizationParams?.scope, + audience: getTokenOptions.authorizationParams?.audience || 'default', clientId: this.options.clientId, getDetailedEntry: options.detailedResponse }); @@ -984,13 +944,18 @@ export class Auth0Client { options: GetTokenWithPopupOptions = {}, config: PopupConfigOptions = {} ) { - options.audience = options.audience || this.options.audience; - - options.scope = getUniqueScopes( - this.defaultScope, - this.scope, - options.scope - ); + options = { + ...options, + authorizationParams: { + ...this.options.authorizationParams, + ...options.authorizationParams, + scope: getUniqueScopes( + this.defaultScope, + this.scope, + options.authorizationParams?.scope + ) + } + }; config = { ...DEFAULT_POPUP_CONFIG_OPTIONS, @@ -1001,8 +966,8 @@ export class Auth0Client { const cache = await this.cacheManager.get( new CacheKey({ - scope: options.scope, - audience: options.audience || 'default', + scope: options.authorizationParams?.scope, + audience: options.authorizationParams?.audience || 'default', clientId: this.options.clientId }) ); @@ -1039,9 +1004,14 @@ export class Auth0Client { delete options.clientId; } - const { federated, ...logoutOptions } = options; + const { federated, ...logoutOptions } = options.logoutParams || {}; const federatedQuery = federated ? `&federated` : ''; - const url = this._url(`/v2/logout?${createQueryParams(logoutOptions)}`); + const url = this._url( + `/v2/logout?${createQueryParams({ + clientId: options.clientId, + ...logoutOptions + })}` + ); return url + federatedQuery; } @@ -1067,7 +1037,7 @@ export class Auth0Client { public logout(options: LogoutOptions = {}): Promise | void { const { localOnly, ...logoutOptions } = options; - if (localOnly && logoutOptions.federated) { + if (localOnly && logoutOptions.logoutParams?.federated) { throw new Error( 'It is invalid to set both the `federated` and `localOnly` options to `true`' ); @@ -1103,15 +1073,13 @@ export class Auth0Client { const code_challengeBuffer = await sha256(code_verifier); const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer); - const { detailedResponse, ...withoutClientOptions } = options; - const params = this._getParams( - withoutClientOptions, + options.authorizationParams, stateIn, nonceIn, code_challenge, - options.redirect_uri || - this.options.redirect_uri || + options.authorizationParams?.redirect_uri || + this.options.authorizationParams?.redirect_uri || window.location.origin ); @@ -1147,22 +1115,9 @@ export class Auth0Client { throw new Error('Invalid state'); } - const { - scope, - audience, - redirect_uri, - cacheMode, - timeoutInSeconds, - detailedResponse, - ...customOptions - } = options; - const tokenResult = await oauthToken( { - ...this.customOptions, - ...customOptions, - scope, - audience, + ...options.authorizationParams, baseUrl: this.domainUrl, client_id: this.options.clientId, code_verifier, @@ -1171,7 +1126,7 @@ export class Auth0Client { redirect_uri: params.redirect_uri, auth0Client: this.options.auth0Client, useFormData: this.options.useFormData, - timeout: customOptions.timeout || this.httpTimeoutMs + timeout: options.authorizationParams?.timeout || this.httpTimeoutMs } as OAuthTokenOptions, this.worker ); @@ -1203,16 +1158,18 @@ export class Auth0Client { private async _getTokenUsingRefreshToken( options: GetTokenSilentlyOptions ): Promise { - options.scope = getUniqueScopes( + options.authorizationParams = options.authorizationParams || {}; + + options.authorizationParams.scope = getUniqueScopes( this.defaultScope, - this.options.scope, - options.scope + this.options.authorizationParams?.scope, + options.authorizationParams?.scope ); const cache = await this.cacheManager.get( new CacheKey({ - scope: options.scope, - audience: options.audience || 'default', + scope: options.authorizationParams?.scope, + audience: options.authorizationParams?.audience || 'default', clientId: this.options.clientId }) ); @@ -1227,27 +1184,18 @@ export class Auth0Client { } throw new MissingRefreshTokenError( - options.audience || 'default', - options.scope + options.authorizationParams?.audience || 'default', + options.authorizationParams?.scope ); } const redirect_uri = - options.redirect_uri || - this.options.redirect_uri || + options.authorizationParams?.redirect_uri || + this.options.authorizationParams?.redirect_uri || window.location.origin; let tokenResult: TokenEndpointResponse; - const { - scope, - audience, - cacheMode, - timeoutInSeconds, - detailedResponse, - ...customOptions - } = options; - const timeout = typeof options.timeoutInSeconds === 'number' ? options.timeoutInSeconds * 1000 @@ -1256,10 +1204,7 @@ export class Auth0Client { try { tokenResult = await oauthToken( { - ...this.customOptions, - ...customOptions, - audience, - scope, + ...options.authorizationParams, baseUrl: this.domainUrl, client_id: this.options.clientId, grant_type: 'refresh_token', @@ -1277,7 +1222,7 @@ export class Auth0Client { // The web worker didn't have a refresh token in memory so // fallback to an iframe. (e.message.indexOf(MISSING_REFRESH_TOKEN_ERROR_MESSAGE) > -1 || - // A refresh token was found, but is it no longer valid + // A refresh token was found, but is it no longer valid // and useRefreshTokensFallback is explicitly enabled. Fallback to an iframe. (e.message && e.message.indexOf(INVALID_REFRESH_TOKEN_ERROR_MESSAGE) > -1)) && @@ -1294,9 +1239,9 @@ export class Auth0Client { return { ...tokenResult, decodedToken, - scope: options.scope, + scope: options.authorizationParams?.scope, oauthTokenScope: tokenResult.scope, - audience: options.audience || 'default' + audience: options.authorizationParams?.audience || 'default' }; } diff --git a/src/global.ts b/src/global.ts index bc00601d9..9dc6bbfe5 100644 --- a/src/global.ts +++ b/src/global.ts @@ -3,7 +3,7 @@ import { ICache } from './cache'; /** * @ignore */ -export interface BaseLoginOptions { +export interface AuthorizationParams { /** * - `'page'`: displays the UI with a full page view * - `'popup'`: displays the UI with a popup window @@ -21,7 +21,7 @@ export interface BaseLoginOptions { prompt?: 'none' | 'login' | 'consent' | 'select_account'; /** - * Maximum allowable elasped time (in seconds) since authentication. + * Maximum allowable elapsed time (in seconds) since authentication. * If the last time the user authenticated is greater than this value, * the user must be reauthenticated. */ @@ -90,6 +90,15 @@ export interface BaseLoginOptions { */ invitation?: string; + /** + * The default URL where Auth0 will redirect your browser to with + * the authentication result. It must be whitelisted in + * the "Allowed Callback URLs" field in your Auth0 Application's + * settings. If not provided here, it should be provided in the other + * methods that provide authentication. + */ + redirect_uri?: string; + /** * If you need to send custom parameters to the Authorization Server, * make sure to use the original parameter name. @@ -97,6 +106,14 @@ export interface BaseLoginOptions { [key: string]: any; } +interface BaseLoginOptions { + /** + * URL parameters that will be sent back to the Authorization Server. This can be known parameters + * defined by Auth0 or custom parameters that you define. + */ + authorizationParams?: AuthorizationParams; +} + export interface AdvancedOptions { /** * The default scope to be included with all requests. @@ -122,14 +139,6 @@ export interface Auth0ClientOptions extends BaseLoginOptions { * The Client ID found on your Application settings page */ clientId: string; - /** - * The default URL where Auth0 will redirect your browser to with - * the authentication result. It must be whitelisted in - * the "Allowed Callback URLs" field in your Auth0 Application's - * settings. If not provided here, it should be provided in the other - * methods that provide authentication. - */ - redirect_uri?: string; /** * The value in seconds used to account for clock skew in JWT expirations. * Typically, this value is no more than a minute or two at maximum. @@ -272,7 +281,7 @@ export type CacheLocation = 'memory' | 'localstorage'; /** * @ignore */ -export interface AuthorizeOptions extends BaseLoginOptions { +export interface AuthorizeOptions extends AuthorizationParams { response_type: string; response_mode: string; redirect_uri: string; @@ -285,13 +294,6 @@ export interface AuthorizeOptions extends BaseLoginOptions { export interface RedirectLoginOptions extends BaseLoginOptions { - /** - * The URL where Auth0 will redirect your browser to with - * the authentication result. It must be whitelisted in - * the "Allowed Callback URLs" field in your Auth0 Application's - * settings. - */ - redirect_uri?: string; /** * Used to store state before doing the redirect */ @@ -369,24 +371,35 @@ export interface GetTokenSilentlyOptions { cacheMode?: 'on' | 'off' | 'cache-only'; /** - * There's no actual redirect when getting a token silently, - * but, according to the spec, a `redirect_uri` param is required. - * Auth0 uses this parameter to validate that the current `origin` - * matches the `redirect_uri` `origin` when sending the response. - * It must be whitelisted in the "Allowed Web Origins" in your - * Auth0 Application's settings. - */ - redirect_uri?: string; - - /** - * The scope that was used in the authentication request - */ - scope?: string; - - /** - * The audience that was used in the authentication request - */ - audience?: string; + * Parameters that will be sent back to Auth0 as part of a request. + */ + authorizationParams?: { + /** + * There's no actual redirect when getting a token silently, + * but, according to the spec, a `redirect_uri` param is required. + * Auth0 uses this parameter to validate that the current `origin` + * matches the `redirect_uri` `origin` when sending the response. + * It must be whitelisted in the "Allowed Web Origins" in your + * Auth0 Application's settings. + */ + redirect_uri?: string; + + /** + * The scope that was used in the authentication request + */ + scope?: string; + + /** + * The audience that was used in the authentication request + */ + audience?: string; + + /** + * If you need to send custom parameters to the Authorization Server, + * make sure to use the original parameter name. + */ + [key: string]: any; + }; /** A maximum number of seconds to wait before declaring the background /authorize call as failed for timeout * Defaults to 60s. @@ -400,12 +413,6 @@ export interface GetTokenSilentlyOptions { * The default is `false`. */ detailedResponse?: boolean; - - /** - * If you need to send custom parameters to the Authorization Server, - * make sure to use the original parameter name. - */ - [key: string]: any; } export interface GetTokenWithPopupOptions extends PopupLoginOptions { @@ -418,20 +425,6 @@ export interface GetTokenWithPopupOptions extends PopupLoginOptions { } export interface LogoutUrlOptions { - /** - * The URL where Auth0 will redirect your browser to after the logout. - * - * **Note**: If the `clientId` parameter is included, the - * `returnTo` URL that is provided must be listed in the - * Application's "Allowed Logout URLs" in the Auth0 dashboard. - * However, if the `clientId` parameter is not included, the - * `returnTo` URL must be listed in the "Allowed Logout URLs" at - * the account level in the Auth0 dashboard. - * - * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout) - */ - returnTo?: string; - /** * The `clientId` of your application. * @@ -441,52 +434,41 @@ export interface LogoutUrlOptions { * * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout) */ - clientId?: string; - - /** - * When supported by the upstream identity provider, - * forces the user to logout of their identity provider - * and from Auth0. - * [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps) - */ - federated?: boolean; + clientId?: string; + /** + * Parameters to pass to the logout endpoint. This can be known parameters defined by Auth0 or custom parameters + * you wish to provide. + */ + logoutParams?: { + /** + * When supported by the upstream identity provider, + * forces the user to logout of their identity provider + * and from Auth0. + * [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps) + */ + federated?: boolean; + /** + * The URL where Auth0 will redirect your browser to after the logout. + * + * **Note**: If the `client_id` parameter is included, the + * `returnTo` URL that is provided must be listed in the + * Application's "Allowed Logout URLs" in the Auth0 dashboard. + * However, if the `client_id` parameter is not included, the + * `returnTo` URL must be listed in the "Allowed Logout URLs" at + * the account level in the Auth0 dashboard. + * + * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout) + */ + returnTo?: string; + + /** + * If you need to send custom parameters to the logout endpoint, make sure to use the original parameter name. + */ + [key: string]: any; + }; } -export interface LogoutOptions { - /** - * The URL where Auth0 will redirect your browser to after the logout. - * - * **Note**: If the `clientId` parameter is included, the - * `returnTo` URL that is provided must be listed in the - * Application's "Allowed Logout URLs" in the Auth0 dashboard. - * However, if the `clientId` parameter is not included, the - * `returnTo` URL must be listed in the "Allowed Logout URLs" at - * the account level in the Auth0 dashboard. - * - * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout) - */ - returnTo?: string; - - /** - * The `clientId` of your application. - * - * If this property is not set, then the `clientId` that was used during initialization of the SDK is sent to the logout endpoint. - * - * If this property is set to `null`, then no client ID value is sent to the logout endpoint. - * - * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout) - */ - clientId?: string; - - /** - * When supported by the upstream identity provider, - * forces the user to logout of their identity provider - * and from Auth0. - * This option cannot be specified along with the `localOnly` option. - * [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps) - */ - federated?: boolean; - +export interface LogoutOptions extends LogoutUrlOptions { /** * When `true`, this skips the request to the logout endpoint on the authorization server, * effectively performing a "local" logout of the application. No redirect should take place, diff --git a/src/utils.ts b/src/utils.ts index fa3df1fbc..1bd6b3956 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -165,7 +165,9 @@ const stripUndefined = (params: any) => { }; export const createQueryParams = ({ clientId: client_id, ...params }: any) => { - return new URLSearchParams(stripUndefined({ client_id, ...params })).toString(); + return new URLSearchParams( + stripUndefined({ client_id, ...params }) + ).toString(); }; export const sha256 = async (s: string) => { diff --git a/static/index.html b/static/index.html index 8e6a1a5f0..29b35eda5 100644 --- a/static/index.html +++ b/static/index.html @@ -526,17 +526,20 @@

Client Options

cacheLocation: _self.useLocalStorage ? 'localstorage' : 'memory', useRefreshTokens: _self.useRefreshTokens, useCookiesForTransactions: _self.useCookiesForTransactions, - redirect_uri: window.location.origin, useFormData: _self.useFormData, - useRefreshTokensFallback: _self.useRefreshTokensFallback + useRefreshTokensFallback: _self.useRefreshTokensFallback, + authorizationParams: { + redirect_uri: window.location.origin + } }; if (_self.audience) { - clientOptions.audience = _self.audience; + clientOptions.authorizationParams.audience = _self.audience; } if (_self.organization && !_self.useOrgAtLogin) { - clientOptions.organization = _self.organization; + clientOptions.authorizationParams.organization = + _self.organization; } if (_self.useCustomStorage) { @@ -577,6 +580,10 @@

Client Options

localStorage.setItem( 'spa-playground-data', JSON.stringify({ + authorizationParams: { + audience: this.audience, + organization: this.organization + }, domain: this.domain, clientId: this.clientId, useLocalStorage: this.useLocalStorage, @@ -585,8 +592,6 @@

Client Options

useConstructor: this.useConstructor, useCookiesForTransactions: this.useCookiesForTransactions, useCache: this.useCache, - audience: this.audience, - organization: this.organization, useFormData: this.useFormData, useOrgAtLogin: this.useOrgAtLogin, useRefreshTokensFallback: this.useRefreshTokensFallback @@ -608,7 +613,7 @@

Client Options

this.organization = defaultOrganization; this.useFormData = true; this.useOrgAtLogin = false; - this.useRefreshTokensFallback = false + this.useRefreshTokensFallback = false; this.saveForm(); }, showAuth0Info: function () { @@ -664,11 +669,13 @@

Client Options

var _self = this; var options = { - redirect_uri: window.location.origin + '/callback.html' + authorizationParams: { + redirect_uri: window.location.origin + '/callback.html' + } }; if (_self.organization) { - options.organization = _self.organization; + options.authorizationParams.organization = _self.organization; } _self.auth0 @@ -685,11 +692,15 @@

Client Options

}, loginRedirect: function () { var _self = this; - var options = { scope: _self.audienceScopes[0].scope }; + var options = { + authorizationParams: { + scope: _self.audienceScopes[0].scope + } + }; if (_self.organization) { if (_self.useOrgAtLogin) { - options.organization = _self.organization; + options.authorizationParams.organization = _self.organization; } } @@ -716,8 +727,10 @@

Client Options

_self.auth0 .getTokenSilently({ - audience: audience, - scope: scope, + authorizationParams: { + audience: audience, + scope: scope + }, cacheMode: _self.useCache ? 'on' : 'off' }) .then(function (token) { @@ -746,7 +759,12 @@

Client Options

var _self = this; _self.auth0 - .getTokenWithPopup({ audience: audience, scope: scope }) + .getTokenWithPopup({ + authorizationParams: { + audience: audience, + scope: scope + } + }) .then(function (token) { access_tokens.push(token); }) @@ -756,13 +774,17 @@

Client Options

}, logout: function () { this.auth0.logout({ - returnTo: window.location.origin + logoutParams: { + returnTo: window.location.origin + } }); }, logoutNoClient: function () { this.auth0.logout({ clientId: null, - returnTo: window.location.origin + logoutParams: { + returnTo: window.location.origin + } }); }, loginHandleInvitationUrl: function () { @@ -776,8 +798,10 @@

Client Options

if (orgMatches) { this.auth0.loginWithRedirect({ - organization: orgMatches[1], - invitation: inviteMatches[1] + authorizationParams: { + organization: orgMatches[1], + invitation: inviteMatches[1] + } }); } } diff --git a/static/multiple_clients.html b/static/multiple_clients.html index f3c9a2e5c..48d3b9da9 100644 --- a/static/multiple_clients.html +++ b/static/multiple_clients.html @@ -81,7 +81,9 @@

const useAuth0 = false; const commonOptions = { - redirect_uri: `${window.location.origin}/multiple_clients.html`, + authorizationParams: { + redirect_uri: `${window.location.origin}/multiple_clients.html` + }, cacheLocation: 'localstorage', useFormData: true, domain: useAuth0 ? 'brucke.auth0.com' : window.location.origin,