From 8b28dba137818888af95d4872d19857d60f17e20 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 29 Dec 2020 15:29:43 -0300 Subject: [PATCH] Added support for using a custom scheme in the callback URL [SDK-2223] (#351) * Add support for custom scheme * Add doc comments * Document the custom scheme feature --- README.md | 16 +++++--- src/webauth/__tests__/webauth.spec.js | 58 ++++++++++++++++++++++----- src/webauth/index.js | 14 ++++--- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e304d193..46635f42 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,11 @@ android:windowSoftInputMode="adjustResize"> ``` -The `applicationId` value will be auto-replaced on runtime with the package name or id of your application (e.g. `com.example.app`). You can change this value from the `build.gradle` file. You can also check it at the top of your `AndroidManifest.xml` file. Take note of this value as you'll be requiring it to define the callback URLs below. +The `applicationId` value will be auto-replaced on runtime with the package name or id of your application (e.g. `com.example.app`). You can change this value from the `build.gradle` file. You can also check it at the top of your `AndroidManifest.xml` file. + +If you use a value other than `applicationId` in `android:scheme` you will also need to pass it as the `customScheme` option parameter of the `authorize` and `clearSession` methods. + +Take note of this value as you'll be requiring it to define the callback URLs below. > For more info please read the [React Native docs](https://facebook.github.io/react-native/docs/linking.html). @@ -155,6 +159,8 @@ If your application is generated using the React Native CLI, the default value o - Replace the **Product Bundle Identifier** value with your desired application's bundle identifier name (e.g. `com.example.app`). - If you've changed the project wide settings, make sure the same were applied to each of the targets your app has. +If you use a value other than `$(PRODUCT_BUNDLE_IDENTIFIER)` in the `CFBundleURLSchemes` field of the `Info.plist` you will also need to pass it as the `customScheme` option parameter of the `authorize` and `clearSession` methods. + > For more info please read the [React Native docs](https://facebook.github.io/react-native/docs/linking.html). ### Callback URL(s) @@ -170,18 +176,18 @@ If in addition you plan to use the log out method, you must also add these URLs #### Android ```text -{YOUR_APP_PACKAGE_NAME}://{YOUR_AUTH0_DOMAIN}/android/{YOUR_APP_PACKAGE_NAME}/callback +{YOUR_APP_PACKAGE_NAME_OR_CUSTOM_SCHEME}://{YOUR_AUTH0_DOMAIN}/android/{YOUR_APP_PACKAGE_NAME}/callback ``` -> Make sure to replace {YOUR_APP_PACKAGE_NAME} and {YOUR_AUTH0_DOMAIN} with the actual values for your application. +> Make sure to replace {YOUR_APP_PACKAGE_NAME_OR_CUSTOM_SCHEME} and {YOUR_AUTH0_DOMAIN} with the actual values for your application. #### iOS ```text -{YOUR_BUNDLE_IDENTIFIER}://{YOUR_AUTH0_DOMAIN}/ios/{YOUR_BUNDLE_IDENTIFIER}/callback +{YOUR_BUNDLE_IDENTIFIER_OR_CUSTOM_SCHEME}://{YOUR_AUTH0_DOMAIN}/ios/{YOUR_BUNDLE_IDENTIFIER}/callback ``` -> Make sure to replace {YOUR_BUNDLE_IDENTIFIER} and {YOUR_AUTH0_DOMAIN} with the actual values for your application. +> Make sure to replace {YOUR_BUNDLE_IDENTIFIER_OR_CUSTOM_SCHEME} and {YOUR_AUTH0_DOMAIN} with the actual values for your application. ## Usage diff --git a/src/webauth/__tests__/webauth.spec.js b/src/webauth/__tests__/webauth.spec.js index 1e533b9a..cfabb0ce 100644 --- a/src/webauth/__tests__/webauth.spec.js +++ b/src/webauth/__tests__/webauth.spec.js @@ -1,21 +1,21 @@ jest.mock('react-native'); import Auth from '../../auth'; import WebAuth from '../index'; -import { NativeModules } from 'react-native'; -import { URL } from 'url'; +import {NativeModules} from 'react-native'; +import {URL} from 'url'; const A0Auth0 = NativeModules.A0Auth0; describe('WebAuth', () => { - const auth = new Auth({ baseUrl: 'https://auth0.com', clientId: 'abc123' }); + const auth = new Auth({baseUrl: 'https://auth0.com', clientId: 'abc123'}); const webauth = new WebAuth(auth); - describe('clearSession', () => { - beforeEach(() => { - NativeModules.A0Auth0 = A0Auth0; - A0Auth0.reset(); - }); + beforeEach(() => { + NativeModules.A0Auth0 = A0Auth0; + A0Auth0.reset(); + }); + describe('clearSession', () => { it('should open log out URL', async () => { await webauth.clearSession(); @@ -24,7 +24,7 @@ describe('WebAuth', () => { expect(parsedUrl.hostname).toEqual('auth0.com'); const urlQuery = parsedUrl.searchParams; expect(urlQuery.get('returnTo')).toEqual( - 'com.my.app://auth0.com/test-os/com.My.App/callback' + 'com.my.app://auth0.com/test-os/com.My.App/callback', ); expect(urlQuery.get('client_id')).toEqual('abc123'); expect(urlQuery.has('federated')).toEqual(false); @@ -32,7 +32,7 @@ describe('WebAuth', () => { }); it('should open log out URL with federated=true', async () => { - const options = { federated: true }; + const options = {federated: true}; await webauth.clearSession(options); const parsedUrl = new URL(A0Auth0.url); @@ -40,11 +40,47 @@ describe('WebAuth', () => { expect(parsedUrl.hostname).toEqual('auth0.com'); const urlQuery = parsedUrl.searchParams; expect(urlQuery.get('returnTo')).toEqual( - 'com.my.app://auth0.com/test-os/com.My.App/callback' + 'com.my.app://auth0.com/test-os/com.My.App/callback', ); expect(urlQuery.get('client_id')).toEqual('abc123'); expect(urlQuery.get('federated')).toEqual('true'); expect(urlQuery.has('auth0Client')).toEqual(true); }); }); + + describe('custom scheme', () => { + it('should build the callback URL with a custom scheme when logging in', async () => { + const newTransactionMock = jest + .spyOn(webauth.agent, 'newTransaction') + .mockImplementation(() => + Promise.resolve({state: 'state', verifier: 'verifier'}), + ); + const showMock = jest + .spyOn(webauth.agent, 'show') + .mockImplementation(authorizeUrl => ({ + then: () => Promise.resolve(authorizeUrl), + })); + const options = {customScheme: 'custom-scheme'}; + let url = await webauth.authorize({}, options); + + const parsedUrl = new URL(url); + const urlQuery = parsedUrl.searchParams; + expect(urlQuery.get('redirect_uri')).toEqual( + 'custom-scheme://auth0.com/test-os/com.My.App/callback', + ); + newTransactionMock.mockRestore(); + showMock.mockRestore(); + }); + + it('should build the callback URL with a custom scheme when logging out', async () => { + const options = {customScheme: 'custom-scheme'}; + await webauth.clearSession(options); + + const parsedUrl = new URL(A0Auth0.url); + const urlQuery = parsedUrl.searchParams; + expect(urlQuery.get('returnTo')).toEqual( + 'custom-scheme://auth0.com/test-os/com.My.App/callback', + ); + }); + }); }); diff --git a/src/webauth/index.js b/src/webauth/index.js index aedbc514..bfda60e7 100644 --- a/src/webauth/index.js +++ b/src/webauth/index.js @@ -7,15 +7,17 @@ import verifyToken from '../jwt'; const {A0Auth0} = NativeModules; -const callbackUri = domain => { +const callbackUri = (domain, customScheme) => { const bundleIdentifier = A0Auth0.bundleIdentifier; const lowerCasedIdentifier = bundleIdentifier.toLowerCase(); - if (bundleIdentifier !== lowerCasedIdentifier) { + if (!customScheme && bundleIdentifier !== lowerCasedIdentifier) { console.warn( 'The Bundle Identifier or Application ID of your app contains uppercase characters and will be lowercased to build the Callback URL. Check the Auth0 dashboard to whitelist the right URL value.', ); } - return `${lowerCasedIdentifier}://${domain}/${Platform.OS}/${bundleIdentifier}/callback`; + return `${customScheme || lowerCasedIdentifier}://${domain}/${ + Platform.OS + }/${bundleIdentifier}/callback`; }; /** @@ -55,6 +57,7 @@ export default class WebAuth { * @param {Object} options Other configuration options. * @param {Number} [options.leeway] The amount of leeway, in seconds, to accommodate potential clock skew when validating an ID token's claims. Defaults to 60 seconds if not specified. * @param {Boolean} [options.ephemeralSession] Disable Single-Sign-On (SSO). It only affects iOS with versions 13 and above. + * @param {String} [options.customScheme] Custom scheme to build the callback URL with. * @returns {Promise} * @see https://auth0.com/docs/api/authentication#authorize-client * @@ -63,7 +66,7 @@ export default class WebAuth { authorize(parameters = {}, options = {}) { const {clientId, domain, client, agent} = this; return agent.newTransaction().then(({state, verifier, ...defaults}) => { - const redirectUri = callbackUri(domain); + const redirectUri = callbackUri(domain, options.customScheme); const expectedState = parameters.state || state; let query = { ...defaults, @@ -124,6 +127,7 @@ export default class WebAuth { * * @param {Object} parameters Parameters to send * @param {Bool} [parameters.federated] Optionally remove the IdP session. + * @param {String} [parameters.customScheme] Custom scheme to build the callback URL with. * @returns {Promise} * @see https://auth0.com/docs/logout * @@ -132,7 +136,7 @@ export default class WebAuth { clearSession(options = {}) { const {client, agent, domain, clientId} = this; options.clientId = clientId; - options.returnTo = callbackUri(domain); + options.returnTo = callbackUri(domain, options.customScheme); options.federated = options.federated || false; const logoutUrl = client.logoutUrl(options); return agent.show(logoutUrl, false, true);