Repository: auth0/auth0-spa-js
Pull request #362 (2020-02-27 11:45:05)

[SDK-1099] Add `localOnly` logout option
### Description

Add an option to the LogoutOptions object that would allow the developer to only perform a "local" logout of the application, suppressing the call to the logout endpoint on the authz server.

### Testing

- [x] This change adds test coverage for new/changed/fixed functionality

### Checklist

~- [ ] I have added documentation for new/changed functionality in this PR or in auth0.com/docs~
- [x] All active GitHub checks for tests, formatting, and security are passing



Patch:
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index e27148c..a2edd2e 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -486,8 +486,13 @@ export default class Auth0Client {
    * auth0.logout();
    * ```
    *
-   * Performs a redirect to `/v2/logout` using the parameters provided
-   * as arguments. [Read more about how Logout works at Auth0](https://auth0.com/docs/logout).
+   * Clears the application session and performs a redirect to `/v2/logout`, using
+   * the parameters provided as arguments, to clear the Auth0 session.
+   * If the `federated` option is specified it also clears the Identity Provider session.
+   * If the `localOnly` option is specified, it only clears the application session.
+   * It is invalid to set both the `federated` and `localOnly` options to `true`,
+   * and an error will be thrown if you do.
+   * [Read more about how Logout works at Auth0](https://auth0.com/docs/logout).
    *
    * @param options
    */
@@ -497,10 +502,24 @@ export default class Auth0Client {
     } else {
       delete options.client_id;
     }
+
+    const { federated, localOnly, ...logoutOptions } = options;
+
+    if (localOnly && federated) {
+      throw new Error(
+        'It is invalid to set both the `federated` and `localOnly` options to `true`'
+      );
+    }
+
     ClientStorage.remove('auth0.is.authenticated');
-    const { federated, ...logoutOptions } = options;
+
+    if (localOnly) {
+      return;
+    }
+
     const federatedQuery = federated ? `&federated` : '';
     const url = this._url(`/v2/logout?${createQueryParams(logoutOptions)}`);
+
     window.location.assign(`${url}${federatedQuery}`);
   }
 }
diff --git a/src/global.ts b/src/global.ts
index 0aa6bb8..d96146b 100644
--- a/src/global.ts
+++ b/src/global.ts
@@ -221,9 +221,18 @@ interface LogoutOptions {
    * When supported by the upstream identity provider,
    * forces the user to logout of their identity provider
    * and from Auth0.
+   * This option cannot be specified along with the `localOnly` option.
    * [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps)
    */
   federated?: boolean;
+
+  /**
+   * When `true`, this skips the request to the logout endpoint on the authorization server,
+   * effectively performing a "local" logout of the application. No redirect should take place,
+   * you should update local logged in state.
+   * This option cannot be specified along with the `federated` option.
+   */
+  localOnly?: boolean;
 }
 
 /**


Test patch:
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 64ec321..324bd7b 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -1596,6 +1596,30 @@ describe('Auth0', () => {
         `https://test.auth0.com/v2/logout?query=params${TEST_TELEMETRY_QUERY_STRING}&federated`
       );
     });
+    it('removes `auth0.is.authenticated` key from storage when `options.localOnly` is true', async () => {
+      const { auth0, storage } = await setup();
+
+      auth0.logout({ localOnly: true });
+      expect(storage.remove).toHaveBeenCalledWith('auth0.is.authenticated');
+    });
+    it('skips `window.location.assign` when `options.localOnly` is true', async () => {
+      const { auth0 } = await setup();
+
+      auth0.logout({ localOnly: true });
+      expect(window.location.assign).not.toHaveBeenCalledWith();
+    });
+    it('calls `window.location.assign` when `options.localOnly` is false', async () => {
+      const { auth0 } = await setup();
+
+      auth0.logout({ localOnly: false });
+      expect(window.location.assign).toHaveBeenCalled();
+    });
+    it('throws when both `options.localOnly` and `options.federated` are true', async () => {
+      const { auth0 } = await setup();
+
+      const fn = () => auth0.logout({ localOnly: true, federated: true });
+      expect(fn).toThrow();
+    });
   });
 });
 describe('default creation function', () => {