Repository: auth0/auth0-spa-js
Pull request #260 (2019-10-30 17:12:09)

Update typescript definition for max_age param
`max_age` should be a number (represents seconds). Unless this is needed as a string for any other reason, this PR should fix that.

lbalmaceda: _7 months ago._ 😬 I'll follow your suggestion.
Didn't know types had tests. I'm fixing this as we speak.
stevehobbsdev: @damieng Interesting point about the breaking change - this was shipped earlier today
luisrudge: I think we're fine changing this to `number` right now because we *just* released this change as part of the OIDC compliance PR: https://github.com/auth0/auth0-spa-js/pull/248/files#diff-bea166de5e48cc5e984d359640eb357eR243
luisrudge: @lbalmaceda can you rollback the last change and just use number + fix tests instead?
lbalmaceda: `max_age` for the JWT verification options was shipped today and incorrectly set to string. That can be changed because it's private and not directly set by the user. However, the `max_age` param for the `BaseLoginOptions` was set ~7 months ago thus we need to do as Damien suggested and consider both scenarios. I've added a function to parse a possible string value and default to undefined when can't be parsed.
luisrudge: @lbalmaceda ahhhh. gotcha!

Patch:
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index 1470bea..7b2bb04 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -89,9 +89,15 @@ export default class Auth0Client {
       id_token,
       nonce,
       leeway: this.options.leeway,
-      max_age: this.options.max_age
+      max_age: this._parseNumber(this.options.max_age)
     });
   }
+  private _parseNumber(value: any): number {
+    if (typeof value !== 'string') {
+      return value;
+    }
+    return parseInt(value, 10) || undefined;
+  }
 
   /**
    * ```js
diff --git a/src/global.ts b/src/global.ts
index bfff3c5..4e4529f 100644
--- a/src/global.ts
+++ b/src/global.ts
@@ -21,7 +21,7 @@ interface BaseLoginOptions {
    * If the last time the user authenticated is greater than this value,
    * the user must be reauthenticated.
    */
-  max_age?: string;
+  max_age?: string | number;
   /**
    * The space-separated list of language tags, ordered by preference.
    * For example: `'fr-CA fr en'`.
@@ -86,7 +86,7 @@ interface Auth0ClientOptions extends BaseLoginOptions {
   redirect_uri?: string;
   /**
    * The value in seconds used to account for clock skew in JWT expirations.
-   * Typically, this value is no more than a minute or two at maximum. 
+   * Typically, this value is no more than a minute or two at maximum.
    * Defaults to 60s.
    */
   leeway?: number;
@@ -241,7 +241,7 @@ interface JWTVerifyOptions {
   id_token: string;
   nonce?: string;
   leeway?: number;
-  max_age?: string;
+  max_age?: number;
 }
 
 /**
diff --git a/src/jwt.ts b/src/jwt.ts
index af249ca..292c519 100644
--- a/src/jwt.ts
+++ b/src/jwt.ts
@@ -164,8 +164,7 @@ export const verify = (options: JWTVerifyOptions) => {
   const nbfDate = new Date(0);
   const authTimeDate = new Date(0);
   authTimeDate.setUTCSeconds(
-    (parseInt(decoded.claims.auth_time) + parseInt(options.max_age)) / 1000 +
-      leeway
+    (parseInt(decoded.claims.auth_time) + options.max_age) / 1000 + leeway
   );
   expDate.setUTCSeconds(decoded.claims.exp + leeway);
   iatDate.setUTCSeconds(decoded.claims.iat - leeway);


Test patch:
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 9fc06ef..89e654e 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -316,7 +316,20 @@ describe('Auth0', () => {
         max_age: undefined
       });
     });
-    it('calls `tokenVerifier.verify` with the `max_age` from constructor', async () => {
+    it('calls `tokenVerifier.verify` with undefined `max_age` when value set in constructor is an empty string', async () => {
+      const { auth0, tokenVerifier } = await setup({ max_age: '' });
+
+      await auth0.loginWithPopup({});
+      expect(tokenVerifier).toHaveBeenCalledWith({
+        id_token: TEST_ID_TOKEN,
+        nonce: TEST_RANDOM_STRING,
+        aud: 'test-client-id',
+        iss: 'https://test.auth0.com/',
+        leeway: undefined,
+        max_age: undefined
+      });
+    });
+    it('calls `tokenVerifier.verify` with the parsed `max_age` string from constructor', async () => {
       const { auth0, tokenVerifier } = await setup({ max_age: '10' });
 
       await auth0.loginWithPopup({});
@@ -326,7 +339,20 @@ describe('Auth0', () => {
         aud: 'test-client-id',
         iss: 'https://test.auth0.com/',
         leeway: undefined,
-        max_age: '10'
+        max_age: 10
+      });
+    });
+    it('calls `tokenVerifier.verify` with the parsed `max_age` number from constructor', async () => {
+      const { auth0, tokenVerifier } = await setup({ max_age: 10 });
+
+      await auth0.loginWithPopup({});
+      expect(tokenVerifier).toHaveBeenCalledWith({
+        id_token: TEST_ID_TOKEN,
+        nonce: TEST_RANDOM_STRING,
+        aud: 'test-client-id',
+        iss: 'https://test.auth0.com/',
+        leeway: undefined,
+        max_age: 10
       });
     });
     it('saves cache', async () => {
diff --git a/__tests__/jwt.test.ts b/__tests__/jwt.test.ts
index 666ca65..db58557 100644
--- a/__tests__/jwt.test.ts
+++ b/__tests__/jwt.test.ts
@@ -285,9 +285,7 @@ describe('jwt', async () => {
   });
   it('validate auth_time is present when max_age is provided', async () => {
     const id_token = await createJWT({ ...DEFAULT_PAYLOAD });
-    expect(() =>
-      verify({ ...verifyOptions, id_token, max_age: '123' })
-    ).toThrow(
+    expect(() => verify({ ...verifyOptions, id_token, max_age: 123 })).toThrow(
       'Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified'
     );
   });
@@ -298,7 +296,7 @@ describe('jwt', async () => {
       ...DEFAULT_PAYLOAD,
       auth_time: yesterday.getTime()
     });
-    expect(() => verify({ ...verifyOptions, id_token, max_age: '1' })).toThrow(
+    expect(() => verify({ ...verifyOptions, id_token, max_age: 1 })).toThrow(
       'Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication.'
     );
   });