Skip to content

Commit

Permalink
Add openUrl and deprecate onRedirect (#1058)
Browse files Browse the repository at this point in the history
* Add openUrl and deprecate onRedirect

* Update MIGRATION_GUIDE to refer to openUrl instead of onRedirect

* Use openUrl: false when catching login_required

* Remove boolean from openUrl in loginWithRedirect

* revert changes
  • Loading branch information
frederikprijck authored Dec 8, 2022
1 parent dd95f63 commit 275d845
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 25 deletions.
20 changes: 10 additions & 10 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ With the v2 release of Auth0-SPA-JS, we have improved both performance and devel
- [Introduction of logoutParams](#introduction-of-logoutparams)
- [buildAuthorizeUrl has been removed](#buildauthorizeurl-has-been-removed)
- [buildLogoutUrl has been removed](#buildlogouturl-has-been-removed)
- [localOnly logout has been removed, and replaced by onRedirect](#localonly-logout-has-been-removed-and-replaced-by-onredirect)
- [localOnly logout has been removed, and replaced by openUrl](#localonly-logout-has-been-removed-and-replaced-by-openurl)
- [redirectMethod has been removed from loginWithRedirect](#redirectmethod-has-been-removed-from-loginwithredirect)
- [ignoreCache on getTokenSilentlyhas been replaced by cacheMode](#ignorecache-on-gettokensilently-has-been-replaced-by-cachemode)
- [application/x-www-form-urlencoded is used by default instead of application/json](#applicationx-www-form-urlencoded-is-used-by-default-instead-of-applicationjson)
Expand Down Expand Up @@ -148,11 +148,11 @@ const url = await client.buildAuthorizeUrl();
await Browser.open({ url });
```

With v2, we have removed `buildAuthorizeUrl`. This means that the snippet above will no longer work, and you should update your code by using `onRedirect` instead.
With v2, we have removed `buildAuthorizeUrl`. This means that the snippet above will no longer work, and you should update your code by using `openUrl` instead.

```ts
await client.loginWithRedirect({
async onRedirect(url) {
async openUrl(url) {
// Redirect using Capacitor's Browser plugin
await Browser.open({ url });
}
Expand All @@ -172,11 +172,11 @@ const url = await client.buildLogoutUrl();
await Browser.open({ url });
```

With v2, `buildLogoutUrl` has been removed and you should update any code that is not able to rely on `window.location.assign` to use `onRedirect` when calling `logout`:
With v2, `buildLogoutUrl` has been removed and you should update any code that is not able to rely on `window.location.assign` to use `openUrl` when calling `logout`:

```ts
client.logout({
async onRedirect(url) {
async openUrl(url) {
// Redirect using Capacitor's Browser plugin
await Browser.open({ url });
}
Expand All @@ -185,15 +185,15 @@ client.logout({

This method was removed because, when using our SDK, the logout method is expected to be called regardless of the browser used. Instead of calling both `logout` and `buildLogoutUrl`, you can now change the redirect behaviour when calling `logout`.

### `localOnly` logout has been removed, and replaced by `onRedirect`
### `localOnly` logout has been removed, and replaced by `openUrl: false`

When calling the SDK's `logout` method, v1 supports the ability to specify `localOnly: true`, ensuring our SDK does not redirect to Auth0 but only clears the user state from the application.

With v2, we have removed `localOnly`, but instead provided a way for developers to take control of the redirect behavior by setting `onRedirect`. In order to achieve localOnly logout with v2, you should set `onRedirect` to a noop function.
With v2, we have removed `localOnly`, but instead provided a way for developers to take control of the redirect behavior by setting `openUrl`. In order to achieve localOnly logout with v2, you should set `openUrl` to false.

```ts
client.logout({
async onRedirect() {}
openUrl: false
});
```

Expand All @@ -207,11 +207,11 @@ await client.loginWithRedirect({
});
```

With the release of v2, we have removed `redirectMethod`. If you want to use anything but `window.location.assign` to handle the redirect to Auth0, you should implement `onRedirect`:
With the release of v2, we have removed `redirectMethod`. If you want to use anything but `window.location.assign` to handle the redirect to Auth0, you should implement `openUrl`:

```ts
await client.loginWithRedirect({
async onRedirect(url) {
async openUrl(url) {
window.location.replace(url);
}
});
Expand Down
5 changes: 4 additions & 1 deletion __tests__/Auth0Client/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
TEST_STATE
} from '../constants';
import { expect } from '@jest/globals';
import { patchOpenUrlWithOnRedirect } from '../../src/Auth0Client.utils';

const authorizationResponse: AuthenticationResult = {
code: 'my_code',
Expand Down Expand Up @@ -194,7 +195,9 @@ export const loginWithRedirectFn = (mockWindow, mockFetch) => {
} = processDefaultLoginWithRedirectOptions(testConfig);
await auth0.loginWithRedirect(options);

if (!options?.onRedirect) {
const patchesOptions = options && patchOpenUrlWithOnRedirect(options);

if (!patchesOptions || patchesOptions.openUrl == null) {
expect(mockWindow.location.assign).toHaveBeenCalled();
}

Expand Down
42 changes: 40 additions & 2 deletions __tests__/Auth0Client/loginWithRedirect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,42 @@ describe('Auth0Client', () => {
);
});

it('should log the user in by calling window.location.replace when specifying it as openUrl', async () => {
const auth0 = setup();

await loginWithRedirect(auth0, {
authorizationParams: {
audience: 'test_audience'
},
openUrl: async url => window.location.replace(url)
});

const url = new URL(mockWindow.location.replace.mock.calls[0][0]);

assertUrlEquals(
url,
TEST_DOMAIN,
'/authorize',
{
audience: 'test_audience'
},
false
);
});

it('skips `window.location.assign` when `options.openUrl` is provided', async () => {
const auth0 = setup();

await loginWithRedirect(auth0, {
authorizationParams: {
audience: 'test_audience'
},
openUrl: async () => {}
});

expect(window.location.assign).not.toHaveBeenCalled();
});

it('should log the user in with custom params', async () => {
const auth0 = setup();

Expand Down Expand Up @@ -456,11 +492,13 @@ describe('Auth0Client', () => {
await loginWithRedirect(auth0);

expect(<jest.Mock>esCookie.remove).toHaveBeenCalledWith(
`auth0.${TEST_CLIENT_ID}.organization_hint`, {}
`auth0.${TEST_CLIENT_ID}.organization_hint`,
{}
);

expect(<jest.Mock>esCookie.remove).toHaveBeenCalledWith(
`_legacy_auth0.${TEST_CLIENT_ID}.organization_hint`, {}
`_legacy_auth0.${TEST_CLIENT_ID}.organization_hint`,
{}
);
});

Expand Down
69 changes: 67 additions & 2 deletions __tests__/Auth0Client/logout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,21 @@ describe('Auth0Client', () => {
);
});

it('removes the organization hint cookie from storage when `options.onRedirect` is set', async () => {
it('removes authenticated cookie from storage when `options.openUrl` is set', async () => {
const auth0 = setup();

await auth0.logout({ onRedirect: async () => {} });
await auth0.logout({ openUrl: async () => {} });

expect(esCookie.remove).toHaveBeenCalledWith(
`auth0.${TEST_CLIENT_ID}.is.authenticated`,
{}
);
});

it('removes the organization hint cookie from storage when `options.openUrl` is set', async () => {
const auth0 = setup();

await auth0.logout({ openUrl: async () => {} });

expect(esCookie.remove).toHaveBeenCalledWith(
`auth0.${TEST_CLIENT_ID}.organization_hint`,
Expand All @@ -179,13 +190,33 @@ describe('Auth0Client', () => {
);
});

it('skips `window.location.assign` when `options.openUrl` is provided', async () => {
const auth0 = setup();
const openUrl = jest.fn();
await auth0.logout({ openUrl });

expect(window.location.assign).not.toHaveBeenCalled();
expect(openUrl).toHaveBeenCalledWith(
expect.stringContaining(
'https://auth0_domain/v2/logout?client_id=auth0_client_id'
)
);
});

it('calls `window.location.assign` when `options.onRedirect` is not provided', async () => {
const auth0 = setup();

await auth0.logout();
expect(window.location.assign).toHaveBeenCalled();
});

it('calls `window.location.assign` when `options.openUrl` is not provided', async () => {
const auth0 = setup();

await auth0.logout();
expect(window.location.assign).toHaveBeenCalled();
});

it('can access isAuthenticated immediately after local logout', async () => {
const auth0 = setup();

Expand All @@ -196,6 +227,16 @@ describe('Auth0Client', () => {
expect(await auth0.isAuthenticated()).toBe(false);
});

it('can access isAuthenticated immediately after local logout', async () => {
const auth0 = setup();

await loginWithRedirect(auth0);
expect(await auth0.isAuthenticated()).toBe(true);
await auth0.logout({ openUrl: async () => {} });

expect(await auth0.isAuthenticated()).toBe(false);
});

it('can access isAuthenticated immediately after local logout when using a custom async cache', async () => {
const auth0 = setup({
cache: new InMemoryAsyncCacheNoKeys()
Expand All @@ -208,6 +249,18 @@ describe('Auth0Client', () => {
expect(await auth0.isAuthenticated()).toBe(false);
});

it('can access isAuthenticated immediately after local logout when using a custom async cache - using openUrl', async () => {
const auth0 = setup({
cache: new InMemoryAsyncCacheNoKeys()
});

await loginWithRedirect(auth0);
expect(await auth0.isAuthenticated()).toBe(true);
await auth0.logout({ openUrl: async () => {} });

expect(await auth0.isAuthenticated()).toBe(false);
});

it('can access isAuthenticated immediately after local logout when using a custom async cache', async () => {
const auth0 = setup({
cache: new InMemoryAsyncCacheNoKeys()
Expand All @@ -220,6 +273,18 @@ describe('Auth0Client', () => {
expect(await auth0.isAuthenticated()).toBe(false);
});

it('can access isAuthenticated immediately after local logout when using a custom async cache - using openUrl', async () => {
const auth0 = setup({
cache: new InMemoryAsyncCacheNoKeys()
});

await loginWithRedirect(auth0);
expect(await auth0.isAuthenticated()).toBe(true);
await auth0.logout({ openUrl: async () => {} });

expect(await auth0.isAuthenticated()).toBe(false);
});

it('correctly handles a null clientId value', async () => {
const auth0 = setup();
await auth0.logout({ clientId: null });
Expand Down
20 changes: 11 additions & 9 deletions src/Auth0Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ import {
cacheFactory,
getAuthorizeParams,
GET_TOKEN_SILENTLY_LOCK_KEY,
OLD_IS_AUTHENTICATED_COOKIE_NAME
OLD_IS_AUTHENTICATED_COOKIE_NAME,
patchOpenUrlWithOnRedirect
} from './Auth0Client.utils';

/**
Expand Down Expand Up @@ -444,7 +445,8 @@ export class Auth0Client {
public async loginWithRedirect<TAppState = any>(
options: RedirectLoginOptions<TAppState> = {}
) {
const { onRedirect, fragment, appState, ...urlOptions } = options;
const { openUrl, fragment, appState, ...urlOptions } =
patchOpenUrlWithOnRedirect(options);

const organizationId =
urlOptions.authorizationParams?.organization ||
Expand All @@ -462,8 +464,8 @@ export class Auth0Client {

const urlWithFragment = fragment ? `${url}#${fragment}` : url;

if (onRedirect) {
await onRedirect(urlWithFragment);
if (openUrl) {
await openUrl(urlWithFragment);
} else {
window.location.assign(urlWithFragment);
}
Expand Down Expand Up @@ -824,7 +826,7 @@ export class Auth0Client {
* @param options
*/
public async logout(options: LogoutOptions = {}): Promise<void> {
const { onRedirect, ...logoutOptions } = options;
const { openUrl, ...logoutOptions } = patchOpenUrlWithOnRedirect(options);

await this.cacheManager.clear();

Expand All @@ -838,9 +840,9 @@ export class Auth0Client {

const url = this._buildLogoutUrl(logoutOptions);

if (onRedirect) {
await onRedirect(url);
} else {
if (openUrl) {
await openUrl(url);
} else if (openUrl !== false) {
window.location.assign(url);
}
}
Expand Down Expand Up @@ -918,7 +920,7 @@ export class Auth0Client {
} catch (e) {
if (e.error === 'login_required') {
this.logout({
onRedirect: async () => {}
openUrl: false
});
}
throw e;
Expand Down
23 changes: 22 additions & 1 deletion src/Auth0Client.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { ICache, InMemoryCache, LocalStorageCache } from './cache';
import {
Auth0ClientOptions,
AuthorizationParams,
AuthorizeOptions
AuthorizeOptions,
LogoutOptions
} from './global';
import { getUniqueScopes } from './scope';

Expand Down Expand Up @@ -73,3 +74,23 @@ export const getAuthorizeParams = (
code_challenge_method: 'S256'
};
};

/**
* @ignore
*
* Function used to provide support for the deprecated onRedirect through openUrl.
*/
export const patchOpenUrlWithOnRedirect = <
T extends Pick<LogoutOptions, 'openUrl' | 'onRedirect'>
>(
options: T
) => {
const { openUrl, onRedirect, ...originalOptions } = options;

const result = {
...originalOptions,
openUrl: openUrl === false || openUrl ? openUrl : onRedirect
};

return result as T;
};
Loading

0 comments on commit 275d845

Please sign in to comment.