Repository: auth0/auth0-spa-js
Pull request #586 (2020-09-24 09:21:18)

Add an option to extend cookie expire day
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.

### Description

Add an option to extend the cookie `auth0.is.authenticated` expire date to the desired amount of days or 1, the default value.


### References

Closes: #584

### Testing

In order to test this behaviour, you'll need to add the `sessionCheckExpiryDays ` option when creating the Auth0 client passing a value other than 1.

```js
createAuth0Client({
  domain: '<AUTH0_DOMAIN>',
  client_id: '<AUTH0_CLIENT_ID>',
  redirect_uri: '<MY_CALLBACK_URL>',
  cacheLocation: 'memory',
  sessionCheckExpiryDays: 10
});
```

After that, you can run your application, login and double check if the browser cookie `auth0.is.authenticated` will expire according to the configuration, in this case in 10 days.

---

- [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
- [x] All active GitHub checks for tests, formatting, and security are passing
- [ ] The correct base branch is being used, if not `master`


luisfmsouza: I'm still working on the documentation and I plan to add the steps to test it.
stevehobbsdev: @luisfmsouza I have converted the PR to a draft until it is ready to be reviewed.
luisfmsouza: @stevehobbsdev I believe it is ready to be reviewed now. I just realised the documentation is created automatically using the code comments.

Let me know if you need more details. 
luisfmsouza: Can you have a look again @stevehobbsdev?
luisfmsouza: Hey @stevehobbsdev, do you have an idea when we will have the new version, including this change, published?
Enngage: I'm anxious to try this one out as well ;) Would be awesome to have this published soon.
stevehobbsdev: @luisfmsouza We're trying to put a release out this week, there are a couple of other things we're looking at batching along with it.

I can merge this in the meantime though - thanks for the contribution.
luisfmsouza: Thanks @stevehobbsdev ! Let me know if there is any contribution I can make in order to get those other things ready to speed up the release :)

Patch:
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index 3216fd7..c0d0e78 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -31,7 +31,8 @@ import {
   DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS,
   MISSING_REFRESH_TOKEN_ERROR_MESSAGE,
   DEFAULT_SCOPE,
-  RECOVERABLE_ERRORS
+  RECOVERABLE_ERRORS,
+  DEFAULT_SESSION_CHECK_EXPIRY_DAYS
 } from './constants';
 
 import version from './version';
@@ -134,6 +135,7 @@ export default class Auth0Client {
   private defaultScope: string;
   private scope: string;
   private cookieStorage: ClientStorage;
+  private sessionCheckExpiryDays: number;
 
   cacheLocation: CacheLocation;
   private worker: Worker;
@@ -145,6 +147,8 @@ export default class Auth0Client {
       options.legacySameSiteCookie === false
         ? CookieStorage
         : CookieStorageWithLegacySameSite;
+    this.sessionCheckExpiryDays =
+      options.sessionCheckExpiryDays || DEFAULT_SESSION_CHECK_EXPIRY_DAYS;
 
     if (!cacheFactory(this.cacheLocation)) {
       throw new Error(`Invalid cache location "${this.cacheLocation}"`);
@@ -378,7 +382,7 @@ export default class Auth0Client {
     this.cache.save(cacheEntry);
 
     this.cookieStorage.save('auth0.is.authenticated', true, {
-      daysUntilExpire: 1
+      daysUntilExpire: this.sessionCheckExpiryDays
     });
   }
 
@@ -514,7 +518,7 @@ export default class Auth0Client {
     this.cache.save(cacheEntry);
 
     this.cookieStorage.save('auth0.is.authenticated', true, {
-      daysUntilExpire: 1
+      daysUntilExpire: this.sessionCheckExpiryDays
     });
 
     return {
@@ -628,7 +632,7 @@ export default class Auth0Client {
       this.cache.save({ client_id: this.options.client_id, ...authResult });
 
       this.cookieStorage.save('auth0.is.authenticated', true, {
-        daysUntilExpire: 1
+        daysUntilExpire: this.sessionCheckExpiryDays
       });
 
       return authResult.access_token;
diff --git a/src/constants.ts b/src/constants.ts
index dc9c88f..30b9952 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -53,3 +53,8 @@ export const RECOVERABLE_ERRORS = [
   // interactively.
   'access_denied'
 ];
+
+/**
+ * @ignore
+ */
+export const DEFAULT_SESSION_CHECK_EXPIRY_DAYS = 1;
diff --git a/src/global.ts b/src/global.ts
index 514b3f2..808f4b9 100644
--- a/src/global.ts
+++ b/src/global.ts
@@ -144,6 +144,12 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
    * Changes to recommended defaults, like defaultScope
    */
   advancedOptions?: AdvancedOptions;
+
+  /**
+   * Number of days until the cookie `auth0.is.authenticated` will expire
+   * Defaults to 1.
+   */
+  sessionCheckExpiryDays?: number;
 }
 
 /**


Test patch:
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 9a0e416..0d319c1 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -621,6 +621,18 @@ describe('Auth0', () => {
         { daysUntilExpire: 1 }
       );
     });
+    it('saves `auth0.is.authenticated` key in storage for an extended period', async () => {
+      const { auth0, cookieStorage } = await setup({
+        sessionCheckExpiryDays: 2
+      });
+
+      await auth0.loginWithPopup({});
+      expect(cookieStorage.save).toHaveBeenCalledWith(
+        'auth0.is.authenticated',
+        true,
+        { daysUntilExpire: 2 }
+      );
+    });
     it('can be called with no arguments', async () => {
       const { auth0, utils } = await setup();
 
@@ -896,13 +908,15 @@ describe('Auth0', () => {
     });
 
     describe('when there is a valid query string in the url', () => {
-      const localSetup = async () => {
+      const localSetup = async (
+        clientOptions?: Partial<Auth0ClientOptions>
+      ) => {
         window.history.pushState(
           {},
           'Test',
           `?code=${TEST_CODE}&state=${TEST_ENCODED_STATE}`
         );
-        const result = await setup();
+        const result = await setup(clientOptions);
         result.transactionManager.get.mockReturnValue({
           code_verifier: TEST_RANDOM_STRING,
           nonce: TEST_ENCODED_STATE,
@@ -1124,6 +1138,21 @@ describe('Auth0', () => {
           }
         );
       });
+      it('saves `auth0.is.authenticated` key in storage for an extended period', async () => {
+        const { auth0, cookieStorage } = await localSetup({
+          sessionCheckExpiryDays: 2
+        });
+
+        await auth0.handleRedirectCallback();
+
+        expect(cookieStorage.save).toHaveBeenCalledWith(
+          'auth0.is.authenticated',
+          true,
+          {
+            daysUntilExpire: 2
+          }
+        );
+      });
       it('returns the transactions appState', async () => {
         const { auth0 } = await localSetup();
         const response = await auth0.handleRedirectCallback();
@@ -1134,14 +1163,16 @@ describe('Auth0', () => {
       });
     });
     describe('when there is a valid query string in a hash', () => {
-      const localSetup = async () => {
+      const localSetup = async (
+        clientOptions?: Partial<Auth0ClientOptions>
+      ) => {
         window.history.pushState({}, 'Test', `/`);
         window.history.pushState(
           {},
           'Test',
           `#/callback/?code=${TEST_CODE}&state=${TEST_ENCODED_STATE}`
         );
-        const result = await setup();
+        const result = await setup(clientOptions);
         result.transactionManager.get.mockReturnValue({
           code_verifier: TEST_RANDOM_STRING,
           nonce: TEST_ENCODED_STATE,
@@ -1309,6 +1340,21 @@ describe('Auth0', () => {
           }
         );
       });
+      it('saves `auth0.is.authenticated` key in storage for an extended period', async () => {
+        const { auth0, cookieStorage } = await localSetup({
+          sessionCheckExpiryDays: 2
+        });
+
+        await auth0.handleRedirectCallback();
+
+        expect(cookieStorage.save).toHaveBeenCalledWith(
+          'auth0.is.authenticated',
+          true,
+          {
+            daysUntilExpire: 2
+          }
+        );
+      });
       it('returns the transactions appState', async () => {
         const { auth0 } = await localSetup();
 
@@ -1983,6 +2029,20 @@ describe('Auth0', () => {
           }
         );
       });
+      it('saves `auth0.is.authenticated` key in storage for an extended period', async () => {
+        const { auth0, cookieStorage } = await setup({
+          sessionCheckExpiryDays: 2
+        });
+
+        await auth0.getTokenSilently(defaultOptionsIgnoreCacheTrue);
+        expect(cookieStorage.save).toHaveBeenCalledWith(
+          'auth0.is.authenticated',
+          true,
+          {
+            daysUntilExpire: 2
+          }
+        );
+      });
       it('acquires and releases lock', async () => {
         const { auth0 } = await setup();
 
@@ -2000,8 +2060,8 @@ describe('Auth0', () => {
   });
 
   describe('getTokenWithPopup()', () => {
-    const localSetup = async (options: Partial<Auth0ClientOptions> = {}) => {
-      const result = await setup(options);
+    const localSetup = async (clientOptions?: Partial<Auth0ClientOptions>) => {
+      const result = await setup(clientOptions);
       result.auth0.loginWithPopup = jest.fn();
       result.cache.get.mockReturnValue({ access_token: TEST_ACCESS_TOKEN });
       return result;