diff --git a/src/auth/__tests__/__snapshots__/index.spec.js.snap b/src/auth/__tests__/__snapshots__/index.spec.js.snap index e8b4a1b5..f7e68ab0 100644 --- a/src/auth/__tests__/__snapshots__/index.spec.js.snap +++ b/src/auth/__tests__/__snapshots__/index.spec.js.snap @@ -25,6 +25,60 @@ Array [ ] `; +exports[`auth code exchange for native social should handle oauth error 1`] = `[invalid_request: Invalid grant]`; + +exports[`auth code exchange for native social should handle unexpected error 1`] = `[a0.response.invalid: Internal Server Error]`; + +exports[`auth code exchange for native social should return successful response 1`] = ` +Object { + "accessToken": "an access token", + "expiresIn": 1234567890, + "idToken": "an id token", + "scope": "openid", + "state": "a random state for auth", +} +`; + +exports[`auth code exchange for native social should return successful response with optional parameters 1`] = ` +Object { + "accessToken": "an access token", + "expiresIn": 1234567890, + "idToken": "an id token", + "scope": "openid", + "state": "a random state for auth", +} +`; + +exports[`auth code exchange for native social should send correct payload 1`] = ` +Array [ + "https://samples.auth0.com/oauth/token", + Object { + "body": "{\\"subject_token\\":\\"a subject token\\",\\"subject_token_type\\":\\"a subject token type\\",\\"client_id\\":\\"A_CLIENT_ID_OF_YOUR_ACCOUNT\\",\\"grant_type\\":\\"urn:ietf:params:oauth:grant-type:token-exchange\\"}", + "headers": Object { + "Accept": "application/json", + "Auth0-Client": "eyJuYW1lIjoicmVhY3QtbmF0aXZlLWF1dGgwIiwidmVyc2lvbiI6IjEuMC4wIn0=", + "Content-Type": "application/json", + }, + "method": "POST", + }, +] +`; + +exports[`auth code exchange for native social should send correct payload with optional parameters 1`] = ` +Array [ + "https://samples.auth0.com/oauth/token", + Object { + "body": "{\\"subject_token\\":\\"a subject token\\",\\"subject_token_type\\":\\"a subject token type\\",\\"user_profile\\":{\\"name\\":{\\"firstName\\":\\"John\\",\\"lastName\\":\\"Smith\\"}},\\"audience\\":\\"http://myapi.com\\",\\"scope\\":\\"openid\\",\\"client_id\\":\\"A_CLIENT_ID_OF_YOUR_ACCOUNT\\",\\"grant_type\\":\\"urn:ietf:params:oauth:grant-type:token-exchange\\"}", + "headers": Object { + "Accept": "application/json", + "Auth0-Client": "eyJuYW1lIjoicmVhY3QtbmF0aXZlLWF1dGgwIiwidmVyc2lvbiI6IjEuMC4wIn0=", + "Content-Type": "application/json", + }, + "method": "POST", + }, +] +`; + exports[`auth code exchange should handle oauth error 1`] = `[invalid_request: Invalid grant]`; exports[`auth code exchange should handle unexpected error 1`] = `[a0.response.invalid: Internal Server Error]`; diff --git a/src/auth/__tests__/index.spec.js b/src/auth/__tests__/index.spec.js index b03119ab..fd015c47 100644 --- a/src/auth/__tests__/index.spec.js +++ b/src/auth/__tests__/index.spec.js @@ -160,6 +160,95 @@ describe('auth', () => { }); }); + describe('code exchange for native social', () => { + it('should send correct payload', async () => { + fetchMock.postOnce('https://samples.auth0.com/oauth/token', tokens); + expect.assertions(1); + await auth.exchangeNativeSocial({ + subjectToken: 'a subject token', + subjectTokenType: 'a subject token type', + }); + expect(fetchMock.lastCall()).toMatchSnapshot(); + }); + + it('should send correct payload with optional parameters', async () => { + fetchMock.postOnce('https://samples.auth0.com/oauth/token', tokens); + expect.assertions(1); + await auth.exchangeNativeSocial({ + subjectToken: 'a subject token', + subjectTokenType: 'a subject token type', + userProfile: { + name: { + firstName: 'John', + lastName: 'Smith', + }, + }, + audience: 'http://myapi.com', + scope: 'openid', + }); + expect(fetchMock.lastCall()).toMatchSnapshot(); + }); + + it('should return successful response', async () => { + fetchMock.postOnce('https://samples.auth0.com/oauth/token', tokens); + expect.assertions(1); + const parameters = { + subjectToken: 'a subject token', + subjectTokenType: 'a subject token type', + }; + await expect( + auth.exchangeNativeSocial(parameters), + ).resolves.toMatchSnapshot(); + }); + + it('should return successful response with optional parameters', async () => { + fetchMock.postOnce('https://samples.auth0.com/oauth/token', tokens); + expect.assertions(1); + const parameters = { + subjectToken: 'a subject token', + subjectTokenType: 'a subject token type', + userProfile: { + name: { + firstName: 'John', + lastName: 'Smith', + }, + }, + audience: 'http://myapi.com', + scope: 'openid', + }; + await expect( + auth.exchangeNativeSocial(parameters), + ).resolves.toMatchSnapshot(); + }); + + it('should handle oauth error', async () => { + fetchMock.postOnce('https://samples.auth0.com/oauth/token', oauthError); + expect.assertions(1); + const parameters = { + subjectToken: 'a subject token', + subjectTokenType: 'a subject token type', + }; + await expect( + auth.exchangeNativeSocial(parameters), + ).rejects.toMatchSnapshot(); + }); + + it('should handle unexpected error', async () => { + fetchMock.postOnce( + 'https://samples.auth0.com/oauth/token', + unexpectedError, + ); + expect.assertions(1); + const parameters = { + subjectToken: 'a subject token', + subjectTokenType: 'a subject token type', + }; + await expect( + auth.exchangeNativeSocial(parameters), + ).rejects.toMatchSnapshot(); + }); + }); + describe('passwordless flow', () => { describe('with email connection', () => { it('should begin with code', async () => { diff --git a/src/auth/index.js b/src/auth/index.js index d0c34db9..b9a2db44 100644 --- a/src/auth/index.js +++ b/src/auth/index.js @@ -118,6 +118,43 @@ export default class Auth { .then(responseHandler); } + /** + * Exchanges an external token obtained via a native social authentication solution for the user's tokens + * + * @param {Object} parameters parameters used to obtain user tokens from an external provider's token + * @param {String} parameters.subjectToken token returned by the native social authentication solution + * @param {String} parameters.subjectTokenType identifier that indicates the native social authentication solution + * @param {Object} [parameters.userProfile] additional profile attributes to set or override, only on select native social authentication solutions + * @param {String} [parameters.audience] API audience to request + * @param {String} [parameters.scope] scopes requested for the issued tokens. e.g. `openid profile` + * @returns {Promise} + * + * @see https://auth0.com/docs/api/authentication#token-exchange-for-native-social + * + * @memberof Auth + */ + exchangeNativeSocial(parameters = {}) { + const payload = apply( + { + parameters: { + subjectToken: {required: true, toName: 'subject_token'}, + subjectTokenType: {required: true, toName: 'subject_token_type'}, + userProfile: {required: false, toName: 'user_profile'}, + audience: {required: false}, + scope: {required: false}, + }, + }, + parameters, + ); + return this.client + .post('/oauth/token', { + ...payload, + client_id: this.clientId, + grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', + }) + .then(responseHandler); + } + /** * Performs Auth with user credentials using the Password Realm Grant *