Skip to content

Commit

Permalink
Added support for using a custom scheme in the callback URL [SDK-2223] (
Browse files Browse the repository at this point in the history
#351)

* Add support for custom scheme

* Add doc comments

* Document the custom scheme feature
  • Loading branch information
Widcket authored Dec 29, 2020
1 parent ecab300 commit 8b28dba
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 21 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ android:windowSoftInputMode="adjustResize">
</activity>
```

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).
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down
58 changes: 47 additions & 11 deletions src/webauth/__tests__/webauth.spec.js
Original file line number Diff line number Diff line change
@@ -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();

Expand All @@ -24,27 +24,63 @@ 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);
expect(urlQuery.has('auth0Client')).toEqual(true);
});

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);
expect(parsedUrl.protocol).toEqual('https:');
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',
);
});
});
});
14 changes: 9 additions & 5 deletions src/webauth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
};

/**
Expand Down Expand Up @@ -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
*
Expand All @@ -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,
Expand Down Expand Up @@ -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
*
Expand All @@ -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);
Expand Down

0 comments on commit 8b28dba

Please sign in to comment.