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

[SDK-3586] Do not fallback to refreshing tokens via iframe method by default #946

Merged
merged 5 commits into from
Aug 16, 2022
Merged
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
31 changes: 17 additions & 14 deletions __tests__/Auth0Client/getTokenSilently.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,9 +905,10 @@ describe('Auth0Client', () => {
});
});

it('falls back to iframe when missing refresh token errors from the worker', async () => {
it('falls back to iframe when missing refresh token errors from the worker and useRefreshTokensFallback is set to true', async () => {
const auth0 = setup({
useRefreshTokens: true
useRefreshTokens: true,
useRefreshTokensFallback: true
});
expect((<any>auth0).worker).toBeDefined();
await loginWithRedirect(auth0, undefined, {
Expand All @@ -932,10 +933,9 @@ describe('Auth0Client', () => {
expect(utils.runIframe).toHaveBeenCalled();
});

it('does not fall back to iframe when missing refresh token errors from the worker and useRefreshTokensFallback set to false', async () => {
it('does not fall back to iframe when missing refresh token errors from the worker and useRefreshTokensFallback not provided', async () => {
const auth0 = setup({
useRefreshTokens: true,
useRefreshTokensFallback: false
useRefreshTokens: true
});
expect((<any>auth0).worker).toBeDefined();
await loginWithRedirect(auth0, undefined, {
Expand Down Expand Up @@ -1038,10 +1038,11 @@ describe('Auth0Client', () => {
});
});

it('falls back to iframe when missing refresh token without the worker', async () => {
it('falls back to iframe when missing refresh token without the worker and useRefreshTokensFallback is set to true', async () => {
const auth0 = setup({
useRefreshTokens: true,
cacheLocation: 'localstorage'
cacheLocation: 'localstorage',
useRefreshTokensFallback: true
});
expect((<any>auth0).worker).toBeUndefined();
await loginWithRedirect(auth0, undefined, {
Expand All @@ -1066,10 +1067,9 @@ describe('Auth0Client', () => {
expect(utils.runIframe).toHaveBeenCalled();
});

it('does not fall back to iframe when missing refresh token without the worker when useRefreshTokensFallback is set to false', async () => {
it('does not fall back to iframe when missing refresh token without the worker when useRefreshTokensFallback is not provided', async () => {
const auth0 = setup({
useRefreshTokens: true,
useRefreshTokensFallback: false,
cacheLocation: 'localstorage'
});
expect((<any>auth0).worker).toBeUndefined();
Expand Down Expand Up @@ -1100,15 +1100,16 @@ describe('Auth0Client', () => {
expect(utils.runIframe).not.toHaveBeenCalled();
});

it('falls back to iframe when missing refresh token in ie11', async () => {
it('falls back to iframe when missing refresh token in ie11 and useRefreshTokensFallback is set to true', async () => {
const originalUserAgent = window.navigator.userAgent;
Object.defineProperty(window.navigator, 'userAgent', {
value:
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko',
configurable: true
});
const auth0 = setup({
useRefreshTokens: true
useRefreshTokens: true,
useRefreshTokensFallback: true
});
expect((<any>auth0).worker).toBeUndefined();
await loginWithRedirect(auth0, undefined, {
Expand Down Expand Up @@ -1686,9 +1687,10 @@ describe('Auth0Client', () => {
expect((http.switchFetch as jest.Mock).mock.calls[0][6]).toEqual(20000);
});

it('when using Refresh Tokens, falls back to iframe when refresh token is expired', async () => {
it('when using Refresh Tokens, falls back to iframe when refresh token is expired and useRefreshTokensFallback is set to true', async () => {
const auth0 = setup({
useRefreshTokens: true
useRefreshTokens: true,
useRefreshTokensFallback: true
});

await loginWithRedirect(auth0);
Expand Down Expand Up @@ -1730,7 +1732,8 @@ describe('Auth0Client', () => {

it('when using Refresh Tokens and fallback fails, ensure the user is logged out', async () => {
const auth0 = setup({
useRefreshTokens: true
useRefreshTokens: true,
useRefreshTokensFallback: true
});

await loginWithRedirect(auth0);
Expand Down
2 changes: 2 additions & 0 deletions cypress/integration/getTokenSilently.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ describe('getTokenSilently', () => {
cy.setSwitch('local-storage', true);
cy.setSwitch('use-cache', false);
cy.setSwitch('refresh-tokens', true);
cy.setSwitch('refresh-token-fallback', true);

cy.login();

cy.intercept({
Expand Down
21 changes: 10 additions & 11 deletions src/Auth0Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ export class Auth0Client {
private readonly isAuthenticatedCookieName: string;
private readonly nowProvider: () => number | Promise<number>;
private readonly httpTimeoutMs: number;
private readonly useRefreshTokensFallback: boolean;

cacheLocation: CacheLocation;
private worker: Worker;
Expand Down Expand Up @@ -307,9 +306,6 @@ export class Auth0Client {
}

this.customOptions = getCustomInitialOptions(options);

this.useRefreshTokensFallback =
this.options.useRefreshTokensFallback !== false;
}

private _url(path: string) {
Expand Down Expand Up @@ -845,7 +841,7 @@ export class Auth0Client {
* to obtain a new token.
*
* A new token will be obtained either by opening an iframe or a
* refresh token (if `useRefreshTokens` is `true`)
* refresh token (if `useRefreshTokens` is `true`).

* If iframes are used, opens an iframe with the `/authorize` URL
* using the parameters provided as arguments. Random and secure `state`
Expand All @@ -854,7 +850,9 @@ export class Auth0Client {
*
* If refresh tokens are used, the token endpoint is called directly with the
* 'refresh_token' grant. If no refresh token is available to make this call,
* the SDK falls back to using an iframe to the '/authorize' URL.
* the SDK will only fall back to using an iframe to the '/authorize' URL if
* the `useRefreshTokensFallback` setting has been set to `true`. By default this
* setting is `false`.
*
* This method may use a web worker to perform the token call if the in-memory
* cache is used.
Expand Down Expand Up @@ -1219,9 +1217,10 @@ export class Auth0Client {

// If you don't have a refresh token in memory
// and you don't have a refresh token in web worker memory
// fallback to an iframe.
// and useRefreshTokensFallback was explicitly enabled
// fallback to an iframe
if ((!cache || !cache.refresh_token) && !this.worker) {
if (this.useRefreshTokensFallback) {
if (this.options.useRefreshTokensFallback) {
return await this._getTokenFromIFrame(options);
}

Expand Down Expand Up @@ -1276,11 +1275,11 @@ export class Auth0Client {
// The web worker didn't have a refresh token in memory so
// fallback to an iframe.
(e.message.indexOf(MISSING_REFRESH_TOKEN_ERROR_MESSAGE) > -1 ||
// A refresh token was found, but is it no longer valid.
// Fallback to an iframe.
// A refresh token was found, but is it no longer valid
// and useRefreshTokensFallback is explicitly enabled. Fallback to an iframe.
(e.message &&
e.message.indexOf(INVALID_REFRESH_TOKEN_ERROR_MESSAGE) > -1)) &&
this.useRefreshTokensFallback
this.options.useRefreshTokensFallback
) {
return await this._getTokenFromIFrame(options);
}
Expand Down
6 changes: 3 additions & 3 deletions src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,12 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
useRefreshTokens?: boolean;

/**
* If true, fallback to the technique of using a hidden iframe and the `authorization_code` grant with `prompt=none` when unable to use refresh tokens.
* The default setting is `true`.
* If true, fallback to the technique of using a hidden iframe and the `authorization_code` grant with `prompt=none` when unable to use refresh tokens. If false, the iframe fallback is not used and
* errors relating to a failed `refresh_token` grant should be handled appropriately. The default setting is `false`.
*
* **Note**: There might be situations where doing silent auth with a Web Message response from an iframe is not possible,
* like when you're serving your application from the file system or a custom protocol (like in a Desktop or Native app).
* In situations like this you can disable the iframe fallback and handle the failed Refresh Grant and prompt the user to login interactively with `loginWithRedirect` or `loginWithPopup`."
* In situations like this you can disable the iframe fallback and handle the failed `refresh_token` grant and prompt the user to login interactively with `loginWithRedirect` or `loginWithPopup`."
*
* E.g. Using the `file:` protocol in an Electron application does not support that legacy technique.
*
Expand Down
27 changes: 25 additions & 2 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,21 @@ <h3 class="mb-5">Other switches</h3>
>Use token cache when fetching new tokens</label
>
</div>

<div class="custom-control custom-switch mb-5">
<input
type="checkbox"
class="custom-control-input"
id="refresh-token-fallback-switch"
v-model="useRefreshTokensFallback"
/>
<label
for="refresh-token-fallback-switch"
class="custom-control-label"
data-cy="switch-refresh-token-fallback"
>Use iframe as a fallback for refresh tokens</label
>
</div>
</div>
<div class="col-md-6">
<div class="custom-control custom-switch mb-5">
Expand Down Expand Up @@ -436,6 +451,7 @@ <h3 class="mb-3">Client Options</h3>
organization: data.organization || defaultOrganization,
useFormData: data.useFormData || false,
useOrgAtLogin: data.useOrgAtLogin || false,
useRefreshTokensFallback: data.useRefreshTokensFallback || false,
clientOptions: '',
audienceScopes: [
{
Expand Down Expand Up @@ -486,6 +502,10 @@ <h3 class="mb-3">Client Options</h3>
useFormData: function () {
this.initializeClient();
this.saveForm();
},
useRefreshTokensFallback: function () {
this.initializeClient();
this.saveForm();
}
},
computed: {
Expand All @@ -507,7 +527,8 @@ <h3 class="mb-3">Client Options</h3>
useRefreshTokens: _self.useRefreshTokens,
useCookiesForTransactions: _self.useCookiesForTransactions,
redirect_uri: window.location.origin,
useFormData: _self.useFormData
useFormData: _self.useFormData,
useRefreshTokensFallback: _self.useRefreshTokensFallback
};

if (_self.audience) {
Expand Down Expand Up @@ -567,7 +588,8 @@ <h3 class="mb-3">Client Options</h3>
audience: this.audience,
organization: this.organization,
useFormData: this.useFormData,
useOrgAtLogin: this.useOrgAtLogin
useOrgAtLogin: this.useOrgAtLogin,
useRefreshTokensFallback: this.useRefreshTokensFallback
})
);

Expand All @@ -586,6 +608,7 @@ <h3 class="mb-3">Client Options</h3>
this.organization = defaultOrganization;
this.useFormData = false;
this.useOrgAtLogin = false;
this.useRefreshTokensFallback = false
this.saveForm();
},
showAuth0Info: function () {
Expand Down