Repository: auth0/auth0-spa-js
Pull request #969 (2022-08-29 11:10:24)

[SDK-3612] Remove internal defaultScopes property
<!-- By submitting a PR to this repository, you agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). Please see the [contributing guidelines](https://github.com/auth0/.github/blob/master/CONTRIBUTING.md) for how to create and submit a high-quality PR for this repo. -->

### Changes

<!--
Please describe both what is changing and why this is important. Include:
- Classes and methods added, deleted, deprecated, or changed
- Screenshots of new or changed UI, if applicable
- A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released)
-->

This removes the internal and private `defaultScope` property in favour of assigning to `scope` instead, this then simplifies any code where we wish to combine the scopes provided to `Auth0Client` and the function used as we will only need to consider combining `this.scope` and `options.authorizationParams.scope`

The scope is still constructed in the same manner as before (defaultScope + this.scope + options.scope) so existing cache keys do not become invalidated, this is evidenced by tests such as [this one](https://github.com/auth0/auth0-spa-js/blob/SDK-3612/__tests__/Auth0Client/getTokenSilently.test.ts#L2260-L2265) not needing changes

This has no impact on developers using the SDK as this property is purely internal.

### 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 f512667..47f3dca 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -164,7 +164,6 @@ export class Auth0Client {
   private readonly cacheManager: CacheManager;
   private readonly domainUrl: string;
   private readonly tokenIssuer: string;
-  private readonly defaultScope: string;
   private readonly scope: string;
   private readonly cookieStorage: ClientStorage;
   private readonly sessionCheckExpiryDays: number;
@@ -232,7 +231,13 @@ export class Auth0Client {
       ? this.cookieStorage
       : SessionStorage;
 
-    this.scope = this.options.authorizationParams?.scope;
+    this.scope = getUniqueScopes(
+      'openid',
+      this.options.advancedOptions?.defaultScope !== undefined
+        ? this.options.advancedOptions.defaultScope
+        : DEFAULT_SCOPE,
+      this.options.authorizationParams?.scope
+    );
 
     this.transactionManager = new TransactionManager(
       transactionStorage,
@@ -252,13 +257,6 @@ export class Auth0Client {
     this.domainUrl = getDomain(this.options.domain);
     this.tokenIssuer = getTokenIssuer(this.options.issuer, this.domainUrl);
 
-    this.defaultScope = getUniqueScopes(
-      'openid',
-      this.options.advancedOptions?.defaultScope !== undefined
-        ? this.options.advancedOptions.defaultScope
-        : DEFAULT_SCOPE
-    );
-
     // If using refresh tokens, automatically specify the `offline_access` scope.
     // Note we cannot add this to 'defaultScope' above as the scopes are used in the
     // cache keys - changing the order could invalidate the keys
@@ -295,11 +293,7 @@ export class Auth0Client {
       client_id: this.options.clientId,
       ...this.options.authorizationParams,
       ...authorizeOptions,
-      scope: getUniqueScopes(
-        this.defaultScope,
-        this.scope,
-        authorizeOptions?.scope
-      ),
+      scope: getUniqueScopes(this.scope, authorizeOptions?.scope),
       response_type: 'code',
       response_mode: 'query',
       state,
@@ -554,7 +548,7 @@ export class Auth0Client {
       options.audience ||
       this.options.authorizationParams?.audience ||
       'default';
-    const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope);
+    const scope = getUniqueScopes(this.scope, options.scope);
 
     const cache = await this.cacheManager.get(
       new CacheKey({
@@ -587,7 +581,7 @@ export class Auth0Client {
       options.audience ||
       this.options.authorizationParams?.audience ||
       'default';
-    const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope);
+    const scope = getUniqueScopes(this.scope, options.scope);
 
     const cache = await this.cacheManager.get(
       new CacheKey({
@@ -833,11 +827,7 @@ export class Auth0Client {
       authorizationParams: {
         ...this.options.authorizationParams,
         ...options.authorizationParams,
-        scope: getUniqueScopes(
-          this.defaultScope,
-          this.scope,
-          options.authorizationParams?.scope
-        )
+        scope: getUniqueScopes(this.scope, options.authorizationParams?.scope)
       }
     };
 
@@ -883,7 +873,8 @@ export class Auth0Client {
         if (cacheMode !== 'off') {
           const entry = await this._getEntryFromCache({
             scope: getTokenOptions.authorizationParams?.scope,
-            audience: getTokenOptions.authorizationParams?.audience || 'default',
+            audience:
+              getTokenOptions.authorizationParams?.audience || 'default',
             clientId: this.options.clientId,
             getDetailedEntry: options.detailedResponse
           });
@@ -949,11 +940,7 @@ export class Auth0Client {
       authorizationParams: {
         ...this.options.authorizationParams,
         ...options.authorizationParams,
-        scope: getUniqueScopes(
-          this.defaultScope,
-          this.scope,
-          options.authorizationParams?.scope
-        )
+        scope: getUniqueScopes(this.scope, options.authorizationParams?.scope)
       }
     };
 
@@ -1158,13 +1145,13 @@ export class Auth0Client {
   private async _getTokenUsingRefreshToken(
     options: GetTokenSilentlyOptions
   ): Promise<GetTokenSilentlyResult> {
-    options.authorizationParams = options.authorizationParams || {};
-
-    options.authorizationParams.scope = getUniqueScopes(
-      this.defaultScope,
-      this.options.authorizationParams?.scope,
-      options.authorizationParams?.scope
-    );
+    options = {
+      ...options,
+      authorizationParams: {
+        ...options.authorizationParams,
+        scope: getUniqueScopes(this.scope, options.authorizationParams?.scope)
+      }
+    };
 
     const cache = await this.cacheManager.get(
       new CacheKey({


Test patch:
diff --git a/__tests__/Auth0Client/constructor.test.ts b/__tests__/Auth0Client/constructor.test.ts
index ac6b3e2..1a40774 100644
--- a/__tests__/Auth0Client/constructor.test.ts
+++ b/__tests__/Auth0Client/constructor.test.ts
@@ -82,7 +82,9 @@ describe('Auth0Client', () => {
         }
       });
 
-      expect((<any>auth0).scope).toBe('test-scope offline_access');
+      expect((<any>auth0).scope).toBe(
+        'openid profile email test-scope offline_access'
+      );
     });
 
     it('ensures the openid scope is defined when customizing default scopes', () => {
@@ -92,7 +94,7 @@ describe('Auth0Client', () => {
         }
       });
 
-      expect((<any>auth0).defaultScope).toBe('openid test-scope');
+      expect((<any>auth0).scope).toBe('openid test-scope');
     });
 
     it('allows an empty custom default scope', () => {
@@ -102,7 +104,7 @@ describe('Auth0Client', () => {
         }
       });
 
-      expect((<any>auth0).defaultScope).toBe('openid');
+      expect((<any>auth0).scope).toBe('openid');
     });
 
     it('should create issuer from domain', () => {
diff --git a/__tests__/Auth0Client/getTokenSilently.test.ts b/__tests__/Auth0Client/getTokenSilently.test.ts
index a8948d2..58eab43 100644
--- a/__tests__/Auth0Client/getTokenSilently.test.ts
+++ b/__tests__/Auth0Client/getTokenSilently.test.ts
@@ -2287,5 +2287,68 @@ describe('Auth0Client', () => {
 
       expect(runIframeSpy).not.toHaveBeenCalled();
     });
+
+    it('should set the correct scopes when using refresh tokens', async () => {
+      const auth0 = setup({
+        useRefreshTokens: true,
+        authorizationParams: {
+          scope: 'read:messages'
+        },
+        advancedOptions: {
+          defaultScope: 'email'
+        }
+      });
+
+      await loginWithRedirect(auth0, undefined, {
+        token: {
+          response: { refresh_token: 'a_refresh_token' }
+        }
+      });
+
+      jest.spyOn(auth0['cacheManager'], 'set');
+
+      jest.spyOn(<any>utils, 'runIframe').mockResolvedValue({
+        access_token: TEST_ACCESS_TOKEN,
+        state: TEST_STATE
+      });
+
+      mockFetch.mockResolvedValue(
+        fetchResponse(true, {
+          id_token: TEST_ID_TOKEN,
+          refresh_token: TEST_REFRESH_TOKEN,
+          access_token: TEST_ACCESS_TOKEN,
+          expires_in: 86400
+        })
+      );
+
+      expect(utils.runIframe).not.toHaveBeenCalled();
+
+      const access_token = await auth0.getTokenSilently({
+        cacheMode: 'off'
+      });
+
+      assertPost(
+        'https://auth0_domain/oauth/token',
+        {
+          redirect_uri: TEST_REDIRECT_URI,
+          client_id: TEST_CLIENT_ID,
+          grant_type: 'refresh_token',
+          refresh_token: 'a_refresh_token'
+        },
+        {
+          'Content-Type': 'application/x-www-form-urlencoded'
+        },
+        1,
+        false
+      );
+
+      expect(access_token).toEqual(TEST_ACCESS_TOKEN);
+
+      expect(auth0['cacheManager'].set).toHaveBeenCalledWith(
+        expect.objectContaining({
+          scope: 'openid email read:messages offline_access'
+        })
+      );
+    });
   });
 });
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 22c8dbd..2a788cd 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -297,7 +297,9 @@ describe('Auth0', () => {
           ...options
         });
 
-        expect((<any>auth0).scope).toBe('the-scope offline_access');
+        expect((<any>auth0).scope).toBe(
+          'openid profile email the-scope offline_access'
+        );
 
         expect(auth0.getTokenSilently).toHaveBeenCalledWith(undefined);
       });