Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add popup config option to prevent closing the popup #1319

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,52 @@ const client = new Auth0Client({
```

In this case, the loading of the `Worker` would comply with a CSP that included `'self'`. You can follow similar steps if you'd prefer to copy the file to your own CDN instead.

## How can this be used in a Chrome extension?

Auth0 for a Chrome extension popup can be approached in a few different ways.

You can avoid it altogether by having users login via a companion web app or in the extension options page instead of the extension popup. Either of these options allow you to use this library as you normally would for any other SPA.
You may also be able to get login working in the popup via the Chrome identity APIs `launchWebAuthFlow`, though this requires more configuration and direct management of the OAuth flow. This library will not work when using the identity API.

If you want to use this library to handle login from the extension popup itself, that can be done with a few workarounds.

### Login in extensions

Redirects do not work in extensions, so you have to use `loginWithPopup`.

The problem with popups in Chrome extensions is that Chrome will close an open extension when the browser window gains focus. Notably, this happens when a popup is closed and focus is automatically returned to the browser.
When a Chrome extension popup gets closed, any scripts that are running inside it are terminated immediately so there is no opportunity to complete the login flow or persist the application state.

By default, the Auth0 popup will be closed as soon as the Auth0Client receives an `authorization_reponse` from the popup, which is *before* that response is used to request a token and save it to the cache.
To allow the flow to complete, you need to set `suppressPopupClose` to prevent the popup from closing too soon. It is recommended that you also pass in the popup, so you can control it and close it once the flow is complete.

```ts
const handleLogin = async () => {
const popup = window.open('', 'auth0:authorize:popup', `width=400,height=600,popup,resizable,status=1`);
await client.loginWithPopup({}, {popup, suppressPopupClose: true})
// do anything else needed to complete the login flow for you app (e.g. analytics callback, etc)
popup.close()
// code after this will most likely not be reached
}
```

### Logout in extensions

The `logout` method will, by default, redirect to the Auth0 logout page. This does not work in an extension.

You can prevent the redirect entirely by setting the `openUrl` option to false.
However, this will *only* log the user out of the [application session layer](https://auth0.com/docs/manage-users/sessions/session-layers).

If you need to log the user out of the Auth0 session layer as well, you can pass a function to the `openUrl` option that fetches the url instead of redirecting to it:

```ts
client.logout({
async openUrl(url) {
await fetch(url, {
credentials: 'include',
mode: 'no-cors',
})
}
})
```
35 changes: 35 additions & 0 deletions __tests__/Auth0Client/getTokenWithPopup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,40 @@ describe('Auth0Client', () => {

expect(config.popup.location.href).toMatch(/global-audience/);
});

it('should close the popup when complete', async () => {
const auth0 = await localSetup();

const config = {
popup: {
location: {
href: ''
},
close: jest.fn()
}
};

await auth0.getTokenWithPopup({}, config);

expect(config.popup.close).toHaveBeenCalledTimes(1);
});

it('should not close the popup when suppressPopupClose is true', async () => {
const auth0 = await localSetup();

const config = {
popup: {
location: {
href: ''
},
close: jest.fn()
},
suppressPopupClose: true
};

await auth0.getTokenWithPopup({}, config);

expect(config.popup.close).not.toHaveBeenCalled();
});
});
});
30 changes: 30 additions & 0 deletions __tests__/Auth0Client/loginWithPopup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,5 +785,35 @@ describe('Auth0Client', () => {
false
);
});

it('should close the popup upon successful login', async () => {
const auth0 = setup();

const popup = {
location: {
href: ''
},
close: jest.fn()
};

await loginWithPopup(auth0, {}, {popup});

expect(popup.close).toHaveBeenCalledTimes(1);
});

it('should not close the popup when suppressPopupClose is true', async () => {
const auth0 = setup();

const popup = {
location: {
href: ''
},
close: jest.fn()
};

await loginWithPopup(auth0, {}, {popup, suppressPopupClose: true});

expect(popup.close).not.toHaveBeenCalled();
});
});
});
15 changes: 15 additions & 0 deletions __tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,21 @@ describe('utils', () => {
expect(popup.close).toHaveBeenCalled();
});

it('does not close popup when suppressPopupClose is true', async () => {
const message = {
data: {
type: 'authorization_response',
response: { id_token: 'id_token' }
}
};

const { popup } = setup(message);

await runPopup({ popup, suppressPopupClose: true });

expect(popup.close).not.toHaveBeenCalled();
});

it('returns authorization error message', async () => {
const message = {
data: {
Expand Down
10 changes: 10 additions & 0 deletions src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,16 @@ export interface PopupConfigOptions {
* security restrictions around when popups can be invoked (e.g. from a user click event)
*/
popup?: any;

/**
* If true, popup will not be closed on successful login response. This may
* be useful in contexts where closing the popup too soon may interrupt the
* login flow in the app (such as Chrome extensions) or if you need to use the
* popup for an additional step. It is recommended that this be used with the
* popup option so that you have a reference to the popup and can close it
* when desired.
*/
suppressPopupClose?: boolean;
}

export interface GetTokenSilentlyOptions {
Expand Down
4 changes: 3 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ export const runPopup = (config: PopupConfigOptions) => {
clearTimeout(timeoutId);
clearInterval(popupTimer);
window.removeEventListener('message', popupEventListener, false);
config.popup.close();
if (!config.suppressPopupClose) {
config.popup.close();
}

if (e.data.response.error) {
return reject(GenericError.fromPayload(e.data.response));
Expand Down