Repository: auth0/auth0-spa-js (ts)

Issue:
Refresh token request doesn't include custom query parameters by default.
### Describe the problem

If you happen to have refresh tokens enabled and come back after token expiry the initial request will be missing the custom query parameters you specified within your client initialization, thereby breaking any rule that relies on them.

### What was the expected behavior?

Silent auth requests involving refresh tokens should probably include any custom parameters you've specified by default. This includes both manually calling `getTokenSilently` and the initial `checkSession` call invoked by `createAuth0Client`. 

### Reproduction

1. Enable refresh tokens, localstorage cache, and set the token lifetime to 90 seconds (for testing). 
2. Add a custom query parameter to the `createAuth0Client` options, say `tenant_id: 'test'`
2. Perform a hard refresh so the authorize call is made and your refresh token cache populated.
3. Wait a bit
4. Do a page refresh. 
5. Watch as your rule sets itself on fire when it tries to access the `tenant_id` field as it isn't being sent.

### Workaround

I'm aware I can work around this by calling `checkSession` manually and passing in the custom options, but I'd argue this is a poor development experience, as you'd expect the custom query params set on the `Auth0Client` to be sent to your rule regardless of if refresh tokens are enabled or not.

### Environment

- **Version of `auth0-spa-js` used:** 1.10
- **Which framework are you using, if applicable (Angular, React, etc):** Vue.js



Pull request:
[SDK-1789] Add custom initial options to the 2 getToken methods
### Description

Custom auth params that are declared in the constructor should be passed to the token endpoint for the 2 get token methods (iframe and code exchange and refresh token)

### References

fixes #514 

### 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
- [x] All active GitHub checks for tests, formatting, and security are passing
- [x] The correct base branch is being used, if not `master`


Patch:
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index 5e68d2d..5fd6479 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -82,12 +82,38 @@ const cacheFactory = (location: string) => {
  */
 const isIE11 = () => /Trident.*rv:11\.0/.test(navigator.userAgent);
 
+/**
+ * @ignore
+ */
+const getCustomInitialOptions = (
+  options: Auth0ClientOptions
+): BaseLoginOptions => {
+  const {
+    advancedOptions,
+    audience,
+    auth0Client,
+    authorizeTimeoutInSeconds,
+    cacheLocation,
+    client_id,
+    domain,
+    issuer,
+    leeway,
+    max_age,
+    redirect_uri,
+    scope,
+    useRefreshTokens,
+    ...customParams
+  } = options;
+  return customParams;
+};
+
 /**
  * Auth0 SDK for Single Page Applications using [Authorization Code Grant Flow with PKCE](https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce).
  */
 export default class Auth0Client {
   private cache: ICache;
   private transactionManager: TransactionManager;
+  private customOptions: BaseLoginOptions;
   private domainUrl: string;
   private tokenIssuer: string;
   private defaultScope: string;
@@ -137,6 +163,8 @@ export default class Auth0Client {
     ) {
       this.worker = new TokenWorker();
     }
+
+    this.customOptions = getCustomInitialOptions(options);
   }
 
   private _url(path) {
@@ -720,6 +748,7 @@ export default class Auth0Client {
 
     const tokenResult = await oauthToken(
       {
+        ...this.customOptions,
         ...customOptions,
         scope,
         audience,
@@ -783,6 +812,7 @@ export default class Auth0Client {
     try {
       tokenResult = await oauthToken(
         {
+          ...this.customOptions,
           ...customOptions,
           audience,
           scope,


Test patch:
diff --git a/__tests__/Auth0Client.test.ts b/__tests__/Auth0Client.test.ts
index e222b86..af966b5 100644
--- a/__tests__/Auth0Client.test.ts
+++ b/__tests__/Auth0Client.test.ts
@@ -661,7 +661,10 @@ describe('Auth0Client', () => {
   });
 
   it('sends custom options through to the token endpoint when using an iframe', async () => {
-    const auth0 = setup();
+    const auth0 = setup({
+      custom_param: 'foo',
+      another_custom_param: 'bar'
+    });
 
     await login(auth0, true);
 
@@ -670,14 +673,23 @@ describe('Auth0Client', () => {
       state: 'MTIz'
     });
 
+    mockFetch.mockResolvedValue(
+      fetchResponse(true, {
+        id_token: 'my_id_token',
+        refresh_token: 'my_refresh_token',
+        access_token: 'my_access_token',
+        expires_in: 86400
+      })
+    );
+
     await auth0.getTokenSilently({
       ignoreCache: true,
-      customParam: 'hello world'
+      custom_param: 'hello world'
     });
 
     expect(
       (<any>utils.runIframe).mock.calls[0][0].includes(
-        'customParam=hello%20world'
+        'custom_param=hello%20world&another_custom_param=bar'
       )
     ).toBe(true);
 
@@ -685,19 +697,27 @@ describe('Auth0Client', () => {
       redirect_uri: 'my_callback_url',
       client_id: 'auth0_client_id',
       grant_type: 'authorization_code',
-      customParam: 'hello world',
+      custom_param: 'hello world',
+      another_custom_param: 'bar',
       code_verifier: '123'
     });
   });
 
   it('sends custom options through to the token endpoint when using refresh tokens', async () => {
     const auth0 = setup({
-      useRefreshTokens: true
+      useRefreshTokens: true,
+      custom_param: 'foo',
+      another_custom_param: 'bar'
     });
 
     await login(auth0, true, { refresh_token: 'a_refresh_token' });
 
-    mockFetch.mockResolvedValueOnce(
+    jest.spyOn(<any>utils, 'runIframe').mockResolvedValue({
+      access_token: 'my_access_token',
+      state: 'MTIz'
+    });
+
+    mockFetch.mockResolvedValue(
       fetchResponse(true, {
         id_token: 'my_id_token',
         refresh_token: 'my_refresh_token',
@@ -710,7 +730,7 @@ describe('Auth0Client', () => {
 
     const access_token = await auth0.getTokenSilently({
       ignoreCache: true,
-      customParam: 'hello world'
+      custom_param: 'hello world'
     });
 
     expect(JSON.parse(mockFetch.mock.calls[1][1].body)).toEqual({
@@ -718,7 +738,8 @@ describe('Auth0Client', () => {
       client_id: 'auth0_client_id',
       grant_type: 'refresh_token',
       refresh_token: 'a_refresh_token',
-      customParam: 'hello world'
+      custom_param: 'hello world',
+      another_custom_param: 'bar'
     });
 
     expect(access_token).toEqual('my_access_token');