Repository: auth0/auth0-spa-js
Pull request #665 (2020-12-04 14:18:55)

[SDK-2183] Add warning when requested scopes differ from retrieved scopes

lgtm-com[bot]: This pull request **introduces 1 alert** when merging 73b0bec2d69645fe77ebc4e9315cced8bdbd1304 into 4b674b28dc122457e4f1e0bbd71af113e378579d - [view on LGTM.com](https://lgtm.com/projects/g/auth0/auth0-spa-js/rev/pr-2df46bbe4460391fa88b05759694773f62867694)

**new alerts:**

* 1 for Unused variable, import, function or class
lgtm-com[bot]: This pull request **introduces 1 alert** when merging ac6df4f9aa1b9fc4fec53c9569cc8f7db8e18af2 into 8b28bd3f03b88292c37b70b2f9bae1bce7d724b0 - [view on LGTM.com](https://lgtm.com/projects/g/auth0/auth0-spa-js/rev/pr-5b6128599f0f62efaf0a0475513b168b60aea8c6)

**new alerts:**

* 1 for Unused variable, import, function or class

Patch:
diff --git a/package.json b/package.json
index 635b975..eb9f7b7 100644
--- a/package.json
+++ b/package.json
@@ -14,8 +14,8 @@
     "build": "rimraf dist && rollup -m -c --environment NODE_ENV:production && npm run test:es-check",
     "build:stats": "npm run build -- --environment WITH_STATS:true && open stats.html",
     "lint:security": "eslint ./src --ext ts --no-eslintrc --config ./.eslintrc.security",
-    "test": "jest --coverage --maxWorkers=2",
-    "test:watch": "jest --coverage --watch",
+    "test": "jest --coverage --maxWorkers=2 --silent",
+    "test:watch": "jest --coverage --watch --silent",
     "test:debug": "node --inspect node_modules/.bin/jest --runInBand",
     "test:open:integration": "cypress open",
     "test:watch:integration": "concurrently --raw npm:dev 'npm:test:open:integration'",
diff --git a/src/api.ts b/src/api.ts
index 7f5f468..c9d2962 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -1,6 +1,7 @@
 import { TokenEndpointOptions } from './global';
 import { DEFAULT_AUTH0_CLIENT } from './constants';
 import { getJSON } from './http';
+import { getMissingScope } from './scope';
 
 export type TokenEndpointResponse = {
   id_token: string;
@@ -21,7 +22,7 @@ export async function oauthToken(
   }: TokenEndpointOptions,
   worker?: Worker
 ) {
-  return await getJSON<TokenEndpointResponse>(
+  const result = await getJSON<TokenEndpointResponse>(
     `${baseUrl}/oauth/token`,
     timeout,
     audience || 'default',
@@ -38,4 +39,16 @@ export async function oauthToken(
     },
     worker
   );
+
+  const missingScope = getMissingScope(scope, result.scope);
+  if (missingScope.length) {
+    console.warn(
+      `The requested scopes (${scope}) are different from the scopes of the retrieved token (${result.scope}). This could mean that your access token may not include all the scopes that you expect. It is advised to resolve this by either:
+  
+  - Removing \`${missingScope}\` from the scope when requesting a new token.
+  - Ensuring \`${missingScope}\` is returned as part of the requested token's scopes.`
+    );
+  }
+
+  return result;
 }
diff --git a/src/scope.ts b/src/scope.ts
index dd7e95e..581bb8a 100644
--- a/src/scope.ts
+++ b/src/scope.ts
@@ -9,3 +9,18 @@ const dedupe = (arr: string[]) => Array.from(new Set(arr));
 export const getUniqueScopes = (...scopes: string[]) => {
   return dedupe(scopes.join(' ').trim().split(/\s+/)).join(' ');
 };
+
+/**
+ * @ignore
+ */
+export const getMissingScope = (
+  originalScope: string = '',
+  comparingScope: string = ''
+) => {
+  const originalScopes = originalScope.split(/\s+/);
+  const comparingScopes = comparingScope.split(/\s+/);
+
+  return originalScopes
+    .filter(scope => !comparingScopes.includes(scope))
+    .join(' ');
+};


Test patch:
diff --git a/__tests__/scope.test.ts b/__tests__/scope.test.ts
index 8309596..14af86d 100644
--- a/__tests__/scope.test.ts
+++ b/__tests__/scope.test.ts
@@ -1,4 +1,4 @@
-import { getUniqueScopes } from '../src/scope';
+import { getMissingScope, getUniqueScopes } from '../src/scope';
 
 describe('getUniqueScopes', () => {
   it('removes duplicates', () => {
@@ -15,3 +15,13 @@ describe('getUniqueScopes', () => {
     ).toBe('openid profile email');
   });
 });
+
+describe('getMissingScopes', () => {
+  it('returns missing scopes', () => {
+    expect(getMissingScope('openid test test2', 'openid')).toBe('test test2');
+  });
+
+  it('returns an empty string if nothing missing', () => {
+    expect(getMissingScope(' openid test', 'openid test')).toBe('');
+  });
+});