Repository: auth0/auth0-spa-js
Pull request #352 (2020-02-07 23:55:02)

[SDK-1352] Stop checking `isAuthenticated` cookie on initialization when using local storage
### Description

* The check for the `is.authenticated` cookie on initialization now only applies when the in-memory cache is being used. This cookie only has a lifetime of 1 day and doesn't really apply when using local storage as we can just check that
* On startup, the cache is now used when rehydrating the user's login session. Now that we have some persistance options, it doesn't make total sense to refresh the token every time the page is loaded if we already have one. This has the side effect of decreasing startup time.
* Added a new type for the cache location, doesn't change the interface

### References

No references other than my own testing to invoke this change. I noticed when testing refresh tokens that, even though I was using refresh tokens and local storage, if I left the site for more than a day and came back to it, I would appear logged out and it's because of that cookie check.

### Testing

- [X] This change adds test coverage for new/changed/fixed functionality

### Checklist

- [x] I have added documentation for new/changed functionality in this PR or in auth0.com/docs
- [ ] All active GitHub checks for tests, formatting, and security are passing
- [x] The correct base branch is being used, if not `master`


picosam: @stevehobbsdev will this also affect (us as users) the fact that we check for `isAuthenticated` when the App loads for instance, or is it just that you have made it stop calling `/oauth/token` internally when there is cache?
stevehobbsdev: @picosam You're right with the second point, the change no longer ignores the cache on initialization. If you're using the in-memory cache, this will more than likely call `/oauth/token` anyway because there'll be nothing in the cache (so same behaviour as now) but if you're using local storage, it will respect whatever you have in the cache and use that if available.

Patch:
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index acf9778..14e8a25 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -38,7 +38,8 @@ import {
   GetTokenWithPopupOptions,
   LogoutOptions,
   RefreshTokenOptions,
-  OAuthTokenOptions
+  OAuthTokenOptions,
+  CacheLocation
 } from './global';
 
 /**
@@ -54,13 +55,16 @@ const GET_TOKEN_SILENTLY_LOCK_KEY = 'auth0.lock.getTokenSilently';
 /**
  * @ignore
  */
-const cacheFactory = location => {
-  const builders = {
-    memory: () => new InMemoryCache().enclosedCache,
-    localstorage: () => new LocalStorageCache()
-  };
+const cacheLocationBuilders = {
+  memory: () => new InMemoryCache().enclosedCache,
+  localstorage: () => new LocalStorageCache()
+};
 
-  return builders[location];
+/**
+ * @ignore
+ */
+const cacheFactory = (location: string) => {
+  return cacheLocationBuilders[location];
 };
 
 /**
@@ -73,7 +77,7 @@ export default class Auth0Client {
   private tokenIssuer: string;
   private readonly DEFAULT_SCOPE = 'openid profile email';
 
-  cacheLocation: string;
+  cacheLocation: CacheLocation;
 
   constructor(private options: Auth0ClientOptions) {
     this.cacheLocation = options.cacheLocation || 'memory';
diff --git a/src/constants.ts b/src/constants.ts
index b9fdd72..6b8fa8a 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -22,3 +22,6 @@ export const DEFAULT_SILENT_TOKEN_RETRY_COUNT = 3;
  * @ignore
  */
 export const DEFAULT_FETCH_TIMEOUT_MS = 10000;
+
+export const CACHE_LOCATION_MEMORY = 'memory';
+export const CACHE_LOCATION_LOCAL_STORAGE = 'localstorage';
diff --git a/src/global.ts b/src/global.ts
index 04eff89..b0bdde3 100644
--- a/src/global.ts
+++ b/src/global.ts
@@ -95,7 +95,7 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
    * The location to use when storing cache data. Valid values are `memory` or `localstorage`.
    * The default setting is `memory`.
    */
-  cacheLocation?: 'memory' | 'localstorage';
+  cacheLocation?: CacheLocation;
 
   /**
    * If true, refresh tokens are used to fetch new access tokens from the Auth0 server. If false, the legacy technique of using a hidden iframe and the `authorization_code` grant with `prompt=none` is used.
@@ -112,6 +112,11 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
   authorizeTimeoutInSeconds?: number;
 }
 
+/**
+ * The possible locations where tokens can be stored
+ */
+export type CacheLocation = 'memory' | 'localstorage';
+
 /**
  * @ignore
  */
diff --git a/src/index.ts b/src/index.ts
index 0d82cf6..c16e767 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,6 +11,8 @@ import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
 import Auth0Client from './Auth0Client';
 import * as ClientStorage from './storage';
 import { Auth0ClientOptions } from './global';
+import { CACHE_LOCATION_MEMORY } from './constants';
+
 import './global';
 
 import { validateCrypto, getUniqueScopes } from './utils';
@@ -26,16 +28,15 @@ export default async function createAuth0Client(options: Auth0ClientOptions) {
 
   const auth0 = new Auth0Client(options);
 
-  if (!ClientStorage.get('auth0.is.authenticated')) {
+  if (
+    auth0.cacheLocation === CACHE_LOCATION_MEMORY &&
+    !ClientStorage.get('auth0.is.authenticated')
+  ) {
     return auth0;
   }
 
   try {
-    await auth0.getTokenSilently({
-      audience: options.audience,
-      scope: options.scope,
-      ignoreCache: true
-    });
+    await auth0.getTokenSilently();
   } catch (error) {
     // ignore
   }


Test patch:
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 6ab19da..47021f9 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -5,10 +5,9 @@ jest.mock('../src/transaction-manager');
 jest.mock('../src/utils');
 
 import Auth0Client from '../src/Auth0Client';
-import createAuth0Client, {
-  PopupConfigOptions,
-  GetTokenSilentlyOptions
-} from '../src/index';
+import { CacheLocation } from '../src/global';
+
+import createAuth0Client, { GetTokenSilentlyOptions } from '../src/index';
 
 import { AuthenticationError } from '../src/errors';
 import version from '../src/version';
@@ -55,7 +54,8 @@ const mockEnclosedCache = {
 jest.mock('../src/cache', () => ({
   InMemoryCache: () => ({
     enclosedCache: mockEnclosedCache
-  })
+  }),
+  LocalStorageCache: () => mockEnclosedCache
 }));
 
 const setup = async (options = {}) => {
@@ -1913,14 +1913,11 @@ describe('default creation function', () => {
       client_id: TEST_CLIENT_ID
     });
 
-    expect(auth0.getTokenSilently).toHaveBeenCalledWith({
-      audience: undefined,
-      ignoreCache: true
-    });
+    expect(auth0.getTokenSilently).toHaveBeenCalledWith();
   });
 
   describe('when refresh tokens are not used', () => {
-    it('calls getTokenSilently with audience and scope', async () => {
+    it('calls getTokenSilently', async () => {
       const utils = require('../src/utils');
 
       const options = {
@@ -1939,10 +1936,7 @@ describe('default creation function', () => {
         ...options
       });
 
-      expect(auth0.getTokenSilently).toHaveBeenCalledWith({
-        ignoreCache: true,
-        ...options
-      });
+      expect(auth0.getTokenSilently).toHaveBeenCalledWith();
     });
   });
 
@@ -1972,11 +1966,31 @@ describe('default creation function', () => {
         'offline_access'
       );
 
-      expect(auth0.getTokenSilently).toHaveBeenCalledWith({
-        ignoreCache: true,
-        scope: 'the-scope offline_access',
-        audience: 'the-audience'
+      expect(auth0.getTokenSilently).toHaveBeenCalledWith();
+    });
+  });
+
+  describe('when localstorage is used', () => {
+    it('refreshes token state regardless of isauthenticated cookie', async () => {
+      const cacheLocation: CacheLocation = 'localstorage';
+
+      const options = {
+        audience: 'the-audience',
+        scope: 'the-scope',
+        cacheLocation
+      };
+
+      Auth0Client.prototype.getTokenSilently = jest.fn();
+
+      require('../src/storage').get = () => false;
+
+      const auth0 = await createAuth0Client({
+        domain: TEST_DOMAIN,
+        client_id: TEST_CLIENT_ID,
+        ...options
       });
+
+      expect(auth0.getTokenSilently).toHaveBeenCalledWith();
     });
   });
 });