Repository: auth0/auth0-spa-js (ts)

Issue:
Logout is clearing credentials stored in local storage for ALL client_ids
### Describe the problem

We use [auth0-angular](https://github.com/auth0/auth0-angular) SDK to provide authentication for multiple Auth0 clients on the same website, with the `cacheLocation: 'localstorage'` configuration option.

In other words, the website https://example-website/A has its own login button for a Auth0 with `client_id_a`, and https://example-website/B has its own login button for a Auth0 configured with  `client_id_b`.

Because A and B share a hostname, they share the same local storage.

Login works as expected: when I login for A, I am not logged in for B, and reciprocally. This is because local storage keys  include the client_id, allowing to differentiate A and B.

But when I log out from A, the stored credentials for B are also cleared from the local storage, which should not happen.

### What was the expected behavior?

Logging out from sectionA should only clear out stored credentials for sectionA, without impacting stored credentials for sectionB.

### Reproduction

- On Auth0, configure two separate clients with each it's own client_id.

- Create an SPA that has a login/logout button, and configure it to use one or the other client_id based on the url /A or /B (or whatever), with option `cacheLocation: 'localstorage'`.

- Go to your-app/A, login for A

- Reload the page to your-app/B, login for B

- Check that local storage contains stored credentials for A and B.

- Reload the page to your-app/A, log**out** for A

- See that local storage was cleared of credentials for both A and B

### Environment

- **Version of `auth0-spa-js` used:** 1.22.5
- **Which browsers have you tested in?** Firefox, Chrome
- **Which framework are you using, if applicable (Angular, React, etc):** Angular
- **Other modules/plugins/libraries that might be involved:** [auth0-angular](https://github.com/auth0/auth0-angular)

### Possible cause

This probably stems from the call made in `Auth0Client.logout()` to `cacheManager.clear()`:
https://github.com/auth0/auth0-spa-js/blob/ab09110da603cf4c979ca9ae7304e43892768f52/src/Auth0Client.ts#L831
Because the client_id to clear is not specified, the cacheManager clears ALL auth0 keys:
https://github.com/auth0/auth0-spa-js/blob/ab09110da603cf4c979ca9ae7304e43892768f52/src/cache/cache-manager.ts#L130


Pull request:
Ensure to only clear current client cache when logging out
### Changes

When calling `logout`, we would call `cacheManager.clear()` without specifying any client id, resulting in clearing all data from the cache, including those belonging to other clientId's as the one configured in the current Auth0Client instance.

When using local storage, this results in logging out both `app1.domain.com` and `app2.domain.com` (both using a different clientId), when that might not be intended.

Unless the user sets `clientId` to null, we should only clear the data from the cache that belongs to either the provided `clientId`, or the globally configured `clientId`.

### References

Fixes #1063 

### Testing

- [x] This change adds unit test coverage
- [x] This change adds integration test coverage
- [x] This change has been tested on the latest version of the platform/language

### Checklist

- [x] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [x] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
- [x] All code quality tools/guidelines have been run/followed


Patch:
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index 41d21ea..ff6cd34 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -828,7 +828,11 @@ export class Auth0Client {
   public async logout(options: LogoutOptions = {}): Promise<void> {
     const { openUrl, ...logoutOptions } = patchOpenUrlWithOnRedirect(options);
 
-    await this.cacheManager.clear();
+    if (options.clientId === null) {
+      await this.cacheManager.clear();
+    } else {
+      await this.cacheManager.clear(options.clientId || this.options.clientId);
+    }
 
     this.cookieStorage.remove(this.orgHintCookieName, {
       cookieDomain: this.options.cookieDomain
diff --git a/src/global.ts b/src/global.ts
index b48a39c..1ec7ca3 100644
--- a/src/global.ts
+++ b/src/global.ts
@@ -410,7 +410,7 @@ export interface LogoutUrlOptions {
    *
    * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
    */
-  clientId?: string;
+  clientId?: string | null;
 
   /**
    * Parameters to pass to the logout endpoint. This can be known parameters defined by Auth0 or custom parameters


Test patch:
diff --git a/__tests__/Auth0Client/logout.test.ts b/__tests__/Auth0Client/logout.test.ts
index 8d30166..4103cd4 100644
--- a/__tests__/Auth0Client/logout.test.ts
+++ b/__tests__/Auth0Client/logout.test.ts
@@ -132,7 +132,7 @@ describe('Auth0Client', () => {
       );
     });
 
-    it('clears the cache', async () => {
+    it('clears the cache for the global clientId', async () => {
       const auth0 = setup();
 
       jest
@@ -141,7 +141,39 @@ describe('Auth0Client', () => {
 
       await auth0.logout();
 
+      expect(auth0['cacheManager']['clear']).toHaveBeenCalledWith(
+        TEST_CLIENT_ID
+      );
+    });
+
+    it('clears the cache for the provided clientId', async () => {
+      const auth0 = setup();
+
+      jest
+        .spyOn(auth0['cacheManager'], 'clear')
+        .mockReturnValueOnce(Promise.resolve());
+
+      await auth0.logout({ clientId: 'client_123' });
+
+      expect(auth0['cacheManager']['clear']).toHaveBeenCalledWith('client_123');
+      expect(auth0['cacheManager']['clear']).not.toHaveBeenCalledWith(
+        TEST_CLIENT_ID
+      );
+    });
+
+    it('clears the cache for all client ids', async () => {
+      const auth0 = setup();
+
+      jest
+        .spyOn(auth0['cacheManager'], 'clear')
+        .mockReturnValueOnce(Promise.resolve());
+
+      await auth0.logout({ clientId: null });
+
       expect(auth0['cacheManager']['clear']).toHaveBeenCalled();
+      expect(auth0['cacheManager']['clear']).not.toHaveBeenCalledWith(
+        TEST_CLIENT_ID
+      );
     });
 
     it('removes authenticated cookie from storage when `options.onRedirect` is set', async () => {