Repository: auth0/auth0-spa-js
Pull request #959 (2022-08-17 16:10:42)

[SDK-3548] Introduce authorizationParams to hold properties sent to Auth0
<!-- 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. -->

### Changes

<!--
Please describe both what is changing and why this is important. Include:
- Classes and methods added, deleted, deprecated, or changed
- Screenshots of new or changed UI, if applicable
- A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released)
-->

Moves all properties that are intended to be sent to Auth0 as part of the auth process into a `authorizationParams` or `logoutParams` object. 

The code changes in this PR in summary:

* The existing `BaseLoginOptions` has now become `AuthorizationParams`, this holds the parameters that will be sent to the Authorization Server (Auth0) and is where custom parameters to send along also can be set
* A new `BaseLoginOptions` interface was added that simply contains `authorizationParams?: AuthorizationParams` and the existing interfaces continue to extend that, with the exception of `GetTokenSilentlyOptions` which only contains a subset of `AuthorizationParams` properties by nature
* `LogoutUrlOptions` and `LogoutOptions` now contain a `logoutParams` object that holds parameters to be sent to the logout endpoint, this object also supports the same custom parameters allowance like `authorizationParams`,


As a developer using the SDK, a summary is that any properties relevant to the Authorization Server (Auth0) or custom parameters that were provided now need to be moved into a `authorizationParams` object and for Logout Endpoint related parameters or again any custom parameters provided to the Logout Endpoint should now be provided in a `logoutParams` object. An example (but not exhaustive) diff of these changes follows.

* `Auth0Client` constructor
```diff
const auth0 = new Auth0Client({
  clientId: '<AUTH0_CLIENT_ID>',
  domain: '<AUTH0_DOMAIN>',
- scope: '<MY_SCOPE>',
- redirect_uri: '<MY_CALLBACK_URL>',
- custom_param: 'custom value'
+ authorizationParams: {
+   redirect_uri: '<MY_CALLBACK_URL>',
+   custom_param: 'custom value',
+   scope: '<MY_SCOPE>'
+ }
});
```

* `createAuth0Client` helper
```diff
const auth0 = await createAuth0Client({
  clientId: '<AUTH0_CLIENT_ID>',
  domain: '<AUTH0_DOMAIN>',
- scope: '<MY_SCOPE>',
- redirect_uri: '<MY_CALLBACK_URL>'
- custom_param: 'custom value'
+ authorizationParams: {
+   redirect_uri: '<MY_CALLBACK_URL>'
+   custom_param: 'custom value',
+   scope: '<MY_SCOPE>'
+ }
});
```

This change should also be made to the `buildAuthorizeUrl`, `checkSession`, `getTokenSilently`, `getTokenWithPopup` `login`, `loginWithPopup`, and `loginWithRedirect` APIs.


* `auth0.buildLogoutUrl`
```diff
auth0.buildLogoutUrl({
- returnTo: '<MY_RETURNTO_URL>'
- custom_param: 'custom value'
+ logoutParams: {
+   returnTo: '<MY_RETURNTO_URL>'
+   custom_param: 'custom value'
+ }
});
```

* `auth0. logout`
```diff
auth0. logout({
  localOnly: true,
- returnTo: '<MY_RETURNTO_URL>'
- custom_param: 'custom value'
+ logoutParams: {
+   returnTo: '<MY_RETURNTO_URL>'
+   custom_param: 'custom value'
+ }
});
```
### Checklist

- [x] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [x] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
- [x] All code quality tools/guidelines have been run/followed


Patch:
diff --git a/README.md b/README.md
index 1c528b3..efa5c09 100644
--- a/README.md
+++ b/README.md
@@ -81,14 +81,18 @@ import { createAuth0Client } from '@auth0/auth0-spa-js';
 const auth0 = await createAuth0Client({
   domain: '<AUTH0_DOMAIN>',
   clientId: '<AUTH0_CLIENT_ID>',
-  redirect_uri: '<MY_CALLBACK_URL>'
+  authorizationParams: {
+    redirect_uri: '<MY_CALLBACK_URL>'
+  }
 });
 
 //with promises
 createAuth0Client({
   domain: '<AUTH0_DOMAIN>',
   clientId: '<AUTH0_CLIENT_ID>',
-  redirect_uri: '<MY_CALLBACK_URL>'
+  authorizationParams: {
+    redirect_uri: '<MY_CALLBACK_URL>'
+  }
 }).then(auth0 => {
   //...
 });
@@ -99,7 +103,9 @@ import { Auth0Client } from '@auth0/auth0-spa-js';
 const auth0 = new Auth0Client({
   domain: '<AUTH0_DOMAIN>',
   clientId: '<AUTH0_CLIENT_ID>',
-  redirect_uri: '<MY_CALLBACK_URL>'
+  authorizationParams: {
+    redirect_uri: '<MY_CALLBACK_URL>'
+  }
 });
 
 //if you do this, you'll need to check the session yourself
@@ -224,9 +230,11 @@ To use the in-memory mode, no additional options need are required as this is th
 ```js
 await createAuth0Client({
   domain: '<AUTH0_DOMAIN>',
-  clientId: '<AUTH0_CLIENT_ID>',
-  redirect_uri: '<MY_CALLBACK_URL>',
-  cacheLocation: 'localstorage' // valid values are: 'memory' or 'localstorage'
+  clientId: '<AUTH0_CLIENT_ID>',,
+  cacheLocation: 'localstorage' // valid values are: 'memory' or 'localstorage',
+  authorizationParams: {
+    redirect_uri: '<MY_CALLBACK_URL>'
+  }
 });
 ```
 
@@ -272,8 +280,10 @@ const sessionStorageCache = {
 await createAuth0Client({
   domain: '<AUTH0_DOMAIN>',
   clientId: '<AUTH0_CLIENT_ID>',
-  redirect_uri: '<MY_CALLBACK_URL>',
-  cache: sessionStorageCache
+  cache: sessionStorageCache,
+  authorizationParams: {
+    redirect_uri: '<MY_CALLBACK_URL>'
+  }
 });
 ```
 
@@ -291,8 +301,10 @@ To enable the use of refresh tokens, set the `useRefreshTokens` option to `true`
 await createAuth0Client({
   domain: '<AUTH0_DOMAIN>',
   clientId: '<AUTH0_CLIENT_ID>',
-  redirect_uri: '<MY_CALLBACK_URL>',
-  useRefreshTokens: true
+  useRefreshTokens: true,
+  authorizationParams: {
+    redirect_uri: '<MY_CALLBACK_URL>'
+  }
 });
 ```
 
@@ -320,8 +332,10 @@ Log in to an organization by specifying the `organization` parameter when settin
 createAuth0Client({
   domain: '<AUTH0_DOMAIN>',
   clientId: '<AUTH0_CLIENT_ID>',
-  redirect_uri: '<MY_CALLBACK_URL>',
-  organization: '<MY_ORG_ID>'
+  authorizationParams: {
+    organization: '<MY_ORG_ID>',
+    redirect_uri: '<MY_CALLBACK_URL>'
+  }
 });
 ```
 
@@ -330,12 +344,16 @@ You can also specify the organization when logging in:
 ```js
 // Using a redirect
 client.loginWithRedirect({
-  organization: '<MY_ORG_ID>'
+  authorizationParams: {
+    organization: '<MY_ORG_ID>'
+  }
 });
 
 // Using a popup window
 client.loginWithPopup({
-  organization: '<MY_ORG_ID>'
+  authorizationParams: {
+    organization: '<MY_ORG_ID>'
+  }
 });
 ```
 
@@ -351,8 +369,10 @@ const invitation = params.get('invitation');
 
 if (organization && invitation) {
   client.loginWithRedirect({
-    organization,
-    invitation
+    authorizationParams: {
+      invitation,
+      organization
+    }
   });
 }
 ```
diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts
index cfeb9d0..f512667 100644
--- a/src/Auth0Client.ts
+++ b/src/Auth0Client.ts
@@ -56,7 +56,7 @@ import {
 
 import {
   Auth0ClientOptions,
-  BaseLoginOptions,
+  AuthorizationParams,
   AuthorizeOptions,
   RedirectLoginOptions,
   PopupLoginOptions,
@@ -156,43 +156,12 @@ const getDomain = (domainUrl: string) => {
   return domainUrl;
 };
 
-/**
- * @ignore
- */
-const getCustomInitialOptions = (
-  options: Auth0ClientOptions
-): BaseLoginOptions => {
-  const {
-    advancedOptions,
-    audience,
-    auth0Client,
-    authorizeTimeoutInSeconds,
-    cacheLocation,
-    cache,
-    clientId,
-    domain,
-    issuer,
-    leeway,
-    max_age,
-    nowProvider,
-    redirect_uri,
-    scope,
-    useRefreshTokens,
-    useRefreshTokensFallback,
-    useCookiesForTransactions,
-    useFormData,
-    ...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 class Auth0Client {
   private readonly transactionManager: TransactionManager;
   private readonly cacheManager: CacheManager;
-  private readonly customOptions: BaseLoginOptions;
   private readonly domainUrl: string;
   private readonly tokenIssuer: string;
   private readonly defaultScope: string;
@@ -209,6 +178,7 @@ export class Auth0Client {
   private worker: Worker;
 
   private readonly defaultOptions: Partial<Auth0ClientOptions> = {
+    authorizationParams: {},
     useRefreshTokensFallback: false,
     useFormData: true
   };
@@ -262,7 +232,7 @@ export class Auth0Client {
       ? this.cookieStorage
       : SessionStorage;
 
-    this.scope = this.options.scope;
+    this.scope = this.options.authorizationParams?.scope;
 
     this.transactionManager = new TransactionManager(
       transactionStorage,
@@ -284,7 +254,7 @@ export class Auth0Client {
 
     this.defaultScope = getUniqueScopes(
       'openid',
-      this.options?.advancedOptions?.defaultScope !== undefined
+      this.options.advancedOptions?.defaultScope !== undefined
         ? this.options.advancedOptions.defaultScope
         : DEFAULT_SCOPE
     );
@@ -305,8 +275,6 @@ export class Auth0Client {
     ) {
       this.worker = new TokenWorker();
     }
-
-    this.customOptions = getCustomInitialOptions(options);
   }
 
   private _url(path: string) {
@@ -317,47 +285,27 @@ export class Auth0Client {
   }
 
   private _getParams(
-    authorizeOptions: BaseLoginOptions,
+    authorizeOptions: AuthorizationParams,
     state: string,
     nonce: string,
     code_challenge: string,
     redirect_uri: string
   ): AuthorizeOptions {
-    // These options should be excluded from the authorize URL,
-    // as they're options for the client and not for the IdP.
-    // ** IMPORTANT ** If adding a new client option, include it in this destructure list.
-    const {
-      useRefreshTokens,
-      useCookiesForTransactions,
-      useFormData,
-      auth0Client,
-      cacheLocation,
-      advancedOptions,
-      detailedResponse,
-      nowProvider,
-      authorizeTimeoutInSeconds,
-      legacySameSiteCookie,
-      sessionCheckExpiryDays,
-      domain,
-      leeway,
-      httpTimeoutInSeconds,
-      useRefreshTokensFallback,
-      ...loginOptions
-    } = this.options;
-
     return {
-      ...loginOptions,
+      client_id: this.options.clientId,
+      ...this.options.authorizationParams,
       ...authorizeOptions,
       scope: getUniqueScopes(
         this.defaultScope,
         this.scope,
-        authorizeOptions.scope
+        authorizeOptions?.scope
       ),
       response_type: 'code',
       response_mode: 'query',
       state,
       nonce,
-      redirect_uri: redirect_uri || this.options.redirect_uri,
+      redirect_uri:
+        redirect_uri || this.options.authorizationParams?.redirect_uri,
       code_challenge,
       code_challenge_method: 'S256'
     };
@@ -381,7 +329,7 @@ export class Auth0Client {
       nonce,
       organizationId,
       leeway: this.options.leeway,
-      max_age: this._parseNumber(this.options.max_age),
+      max_age: this._parseNumber(this.options.authorizationParams?.max_age),
       now
     });
   }
@@ -423,9 +371,7 @@ export class Auth0Client {
     return url;
   }
 
-  private async _prepareAuthorizeUrl(
-    options: RedirectLoginOptions = {}
-  ): Promise<{
+  private async _prepareAuthorizeUrl(options: RedirectLoginOptions): Promise<{
     scope: string;
     audience: string;
     redirect_uri: string;
@@ -435,7 +381,7 @@ export class Auth0Client {
     state: string;
     url: string;
   }> {
-    const { redirect_uri, appState, ...authorizeOptions } = options;
+    const { appState, authorizationParams } = options;
 
     const state = encode(createRandomString());
     const nonce = encode(createRandomString());
@@ -445,11 +391,11 @@ export class Auth0Client {
     const fragment = options.fragment ? `#${options.fragment}` : '';
 
     const params = this._getParams(
-      authorizeOptions,
+      authorizationParams,
       state,
       nonce,
       code_challenge,
-      redirect_uri
+      authorizationParams?.redirect_uri
     );
 
     const url = this._authorizeUrl(params);
@@ -506,7 +452,6 @@ export class Auth0Client {
       }
     }
 
-    const { ...authorizeOptions } = options;
     const stateIn = encode(createRandomString());
     const nonceIn = encode(createRandomString());
     const code_verifier = createRandomString();
@@ -514,11 +459,13 @@ export class Auth0Client {
     const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
 
     const params = this._getParams(
-      authorizeOptions,
+      options.authorizationParams,
       stateIn,
       nonceIn,
       code_challenge,
-      this.options.redirect_uri || window.location.origin
+      options.authorizationParams?.redirect_uri ||
+        this.options.authorizationParams?.redirect_uri ||
+        window.location.origin
     );
 
     const url = this._authorizeUrl({
@@ -557,7 +504,9 @@ export class Auth0Client {
       this.worker
     );
 
-    const organizationId = options.organization || this.options.organization;
+    const organizationId =
+      options.authorizationParams?.organization ||
+      this.options.authorizationParams?.organization;
 
     const decodedToken = await this._verifyIdToken(
       authResult.id_token,
@@ -601,7 +550,10 @@ export class Auth0Client {
   public async getUser<TUser extends User>(
     options: GetUserOptions = {}
   ): Promise<TUser | undefined> {
-    const audience = options.audience || this.options.audience || 'default';
+    const audience =
+      options.audience ||
+      this.options.authorizationParams?.audience ||
+      'default';
     const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope);
 
     const cache = await this.cacheManager.get(
@@ -631,7 +583,10 @@ export class Auth0Client {
   public async getIdTokenClaims(
     options: GetIdTokenClaimsOptions = {}
   ): Promise<IdToken | undefined> {
-    const audience = options.audience || this.options.audience || 'default';
+    const audience =
+      options.audience ||
+      this.options.authorizationParams?.audience ||
+      'default';
     const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope);
 
     const cache = await this.cacheManager.get(
@@ -661,7 +616,9 @@ export class Auth0Client {
   ) {
     const { onRedirect, ...urlOptions } = options;
 
-    const organizationId = urlOptions.organization || this.options.organization;
+    const organizationId =
+      urlOptions.authorizationParams?.organization ||
+      this.options.authorizationParams?.organization;
 
     const { url, ...transaction } = await this._prepareAuthorizeUrl(urlOptions);
 
@@ -870,25 +827,28 @@ export class Auth0Client {
   public async getTokenSilently(
     options: GetTokenSilentlyOptions = {}
   ): Promise<string | GetTokenSilentlyVerboseResponse> {
-    const { cacheMode, ...getTokenOptions }: GetTokenSilentlyOptions = {
-      audience: this.options.audience,
+    options = {
       cacheMode: 'on',
       ...options,
-      scope: getUniqueScopes(this.defaultScope, this.scope, options.scope)
+      authorizationParams: {
+        ...this.options.authorizationParams,
+        ...options.authorizationParams,
+        scope: getUniqueScopes(
+          this.defaultScope,
+          this.scope,
+          options.authorizationParams?.scope
+        )
+      }
     };
 
     return singlePromise(
-      () =>
-        this._getTokenSilently({
-          cacheMode,
-          ...getTokenOptions
-        }),
-      `${this.options.clientId}::${getTokenOptions.audience}::${getTokenOptions.scope}`
+      () => this._getTokenSilently(options),
+      `${this.options.clientId}::${options.authorizationParams?.audience}::${options.authorizationParams?.scope}`
     );
   }
 
   private async _getTokenSilently(
-    options: GetTokenSilentlyOptions = {}
+    options: GetTokenSilentlyOptions
   ): Promise<string | GetTokenSilentlyVerboseResponse> {
     const { cacheMode, ...getTokenOptions } = options;
 
@@ -896,8 +856,8 @@ export class Auth0Client {
     // `lock.acquireLock` when the cache is populated.
     if (cacheMode !== 'off') {
       const entry = await this._getEntryFromCache({
-        scope: getTokenOptions.scope,
-        audience: getTokenOptions.audience || 'default',
+        scope: getTokenOptions.authorizationParams?.scope,
+        audience: getTokenOptions.authorizationParams?.audience || 'default',
         clientId: this.options.clientId,
         getDetailedEntry: options.detailedResponse
       });
@@ -922,8 +882,8 @@ export class Auth0Client {
         // by a previous call while this call was waiting to acquire the lock.
         if (cacheMode !== 'off') {
           const entry = await this._getEntryFromCache({
-            scope: getTokenOptions.scope,
-            audience: getTokenOptions.audience || 'default',
+            scope: getTokenOptions.authorizationParams?.scope,
+            audience: getTokenOptions.authorizationParams?.audience || 'default',
             clientId: this.options.clientId,
             getDetailedEntry: options.detailedResponse
           });
@@ -984,13 +944,18 @@ export class Auth0Client {
     options: GetTokenWithPopupOptions = {},
     config: PopupConfigOptions = {}
   ) {
-    options.audience = options.audience || this.options.audience;
-
-    options.scope = getUniqueScopes(
-      this.defaultScope,
-      this.scope,
-      options.scope
-    );
+    options = {
+      ...options,
+      authorizationParams: {
+        ...this.options.authorizationParams,
+        ...options.authorizationParams,
+        scope: getUniqueScopes(
+          this.defaultScope,
+          this.scope,
+          options.authorizationParams?.scope
+        )
+      }
+    };
 
     config = {
       ...DEFAULT_POPUP_CONFIG_OPTIONS,
@@ -1001,8 +966,8 @@ export class Auth0Client {
 
     const cache = await this.cacheManager.get(
       new CacheKey({
-        scope: options.scope,
-        audience: options.audience || 'default',
+        scope: options.authorizationParams?.scope,
+        audience: options.authorizationParams?.audience || 'default',
         clientId: this.options.clientId
       })
     );
@@ -1039,9 +1004,14 @@ export class Auth0Client {
       delete options.clientId;
     }
 
-    const { federated, ...logoutOptions } = options;
+    const { federated, ...logoutOptions } = options.logoutParams || {};
     const federatedQuery = federated ? `&federated` : '';
-    const url = this._url(`/v2/logout?${createQueryParams(logoutOptions)}`);
+    const url = this._url(
+      `/v2/logout?${createQueryParams({
+        clientId: options.clientId,
+        ...logoutOptions
+      })}`
+    );
 
     return url + federatedQuery;
   }
@@ -1067,7 +1037,7 @@ export class Auth0Client {
   public logout(options: LogoutOptions = {}): Promise<void> | void {
     const { localOnly, ...logoutOptions } = options;
 
-    if (localOnly && logoutOptions.federated) {
+    if (localOnly && logoutOptions.logoutParams?.federated) {
       throw new Error(
         'It is invalid to set both the `federated` and `localOnly` options to `true`'
       );
@@ -1103,15 +1073,13 @@ export class Auth0Client {
     const code_challengeBuffer = await sha256(code_verifier);
     const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
 
-    const { detailedResponse, ...withoutClientOptions } = options;
-
     const params = this._getParams(
-      withoutClientOptions,
+      options.authorizationParams,
       stateIn,
       nonceIn,
       code_challenge,
-      options.redirect_uri ||
-        this.options.redirect_uri ||
+      options.authorizationParams?.redirect_uri ||
+        this.options.authorizationParams?.redirect_uri ||
         window.location.origin
     );
 
@@ -1147,22 +1115,9 @@ export class Auth0Client {
         throw new Error('Invalid state');
       }
 
-      const {
-        scope,
-        audience,
-        redirect_uri,
-        cacheMode,
-        timeoutInSeconds,
-        detailedResponse,
-        ...customOptions
-      } = options;
-
       const tokenResult = await oauthToken(
         {
-          ...this.customOptions,
-          ...customOptions,
-          scope,
-          audience,
+          ...options.authorizationParams,
           baseUrl: this.domainUrl,
           client_id: this.options.clientId,
           code_verifier,
@@ -1171,7 +1126,7 @@ export class Auth0Client {
           redirect_uri: params.redirect_uri,
           auth0Client: this.options.auth0Client,
           useFormData: this.options.useFormData,
-          timeout: customOptions.timeout || this.httpTimeoutMs
+          timeout: options.authorizationParams?.timeout || this.httpTimeoutMs
         } as OAuthTokenOptions,
         this.worker
       );
@@ -1203,16 +1158,18 @@ export class Auth0Client {
   private async _getTokenUsingRefreshToken(
     options: GetTokenSilentlyOptions
   ): Promise<GetTokenSilentlyResult> {
-    options.scope = getUniqueScopes(
+    options.authorizationParams = options.authorizationParams || {};
+
+    options.authorizationParams.scope = getUniqueScopes(
       this.defaultScope,
-      this.options.scope,
-      options.scope
+      this.options.authorizationParams?.scope,
+      options.authorizationParams?.scope
     );
 
     const cache = await this.cacheManager.get(
       new CacheKey({
-        scope: options.scope,
-        audience: options.audience || 'default',
+        scope: options.authorizationParams?.scope,
+        audience: options.authorizationParams?.audience || 'default',
         clientId: this.options.clientId
       })
     );
@@ -1227,27 +1184,18 @@ export class Auth0Client {
       }
 
       throw new MissingRefreshTokenError(
-        options.audience || 'default',
-        options.scope
+        options.authorizationParams?.audience || 'default',
+        options.authorizationParams?.scope
       );
     }
 
     const redirect_uri =
-      options.redirect_uri ||
-      this.options.redirect_uri ||
+      options.authorizationParams?.redirect_uri ||
+      this.options.authorizationParams?.redirect_uri ||
       window.location.origin;
 
     let tokenResult: TokenEndpointResponse;
 
-    const {
-      scope,
-      audience,
-      cacheMode,
-      timeoutInSeconds,
-      detailedResponse,
-      ...customOptions
-    } = options;
-
     const timeout =
       typeof options.timeoutInSeconds === 'number'
         ? options.timeoutInSeconds * 1000
@@ -1256,10 +1204,7 @@ export class Auth0Client {
     try {
       tokenResult = await oauthToken(
         {
-          ...this.customOptions,
-          ...customOptions,
-          audience,
-          scope,
+          ...options.authorizationParams,
           baseUrl: this.domainUrl,
           client_id: this.options.clientId,
           grant_type: 'refresh_token',
@@ -1277,7 +1222,7 @@ export class Auth0Client {
         // The web worker didn't have a refresh token in memory so
         // fallback to an iframe.
         (e.message.indexOf(MISSING_REFRESH_TOKEN_ERROR_MESSAGE) > -1 ||
-          // A refresh token was found, but is it no longer valid 
+          // A refresh token was found, but is it no longer valid
           // and useRefreshTokensFallback is explicitly enabled. Fallback to an iframe.
           (e.message &&
             e.message.indexOf(INVALID_REFRESH_TOKEN_ERROR_MESSAGE) > -1)) &&
@@ -1294,9 +1239,9 @@ export class Auth0Client {
     return {
       ...tokenResult,
       decodedToken,
-      scope: options.scope,
+      scope: options.authorizationParams?.scope,
       oauthTokenScope: tokenResult.scope,
-      audience: options.audience || 'default'
+      audience: options.authorizationParams?.audience || 'default'
     };
   }
 
diff --git a/src/global.ts b/src/global.ts
index bc00601..9dc6bbf 100644
--- a/src/global.ts
+++ b/src/global.ts
@@ -3,7 +3,7 @@ import { ICache } from './cache';
 /**
  * @ignore
  */
-export interface BaseLoginOptions {
+export interface AuthorizationParams {
   /**
    * - `'page'`: displays the UI with a full page view
    * - `'popup'`: displays the UI with a popup window
@@ -21,7 +21,7 @@ export interface BaseLoginOptions {
   prompt?: 'none' | 'login' | 'consent' | 'select_account';
 
   /**
-   * Maximum allowable elasped time (in seconds) since authentication.
+   * Maximum allowable elapsed time (in seconds) since authentication.
    * If the last time the user authenticated is greater than this value,
    * the user must be reauthenticated.
    */
@@ -90,6 +90,15 @@ export interface BaseLoginOptions {
    */
   invitation?: string;
 
+  /**
+   * The default URL where Auth0 will redirect your browser to with
+   * the authentication result. It must be whitelisted in
+   * the "Allowed Callback URLs" field in your Auth0 Application's
+   * settings. If not provided here, it should be provided in the other
+   * methods that provide authentication.
+   */
+  redirect_uri?: string;
+
   /**
    * If you need to send custom parameters to the Authorization Server,
    * make sure to use the original parameter name.
@@ -97,6 +106,14 @@ export interface BaseLoginOptions {
   [key: string]: any;
 }
 
+interface BaseLoginOptions {
+  /**
+   * URL parameters that will be sent back to the Authorization Server. This can be known parameters
+   * defined by Auth0 or custom parameters that you define.
+   */
+  authorizationParams?: AuthorizationParams;
+}
+
 export interface AdvancedOptions {
   /**
    * The default scope to be included with all requests.
@@ -122,14 +139,6 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
    * The Client ID found on your Application settings page
    */
   clientId: string;
-  /**
-   * The default URL where Auth0 will redirect your browser to with
-   * the authentication result. It must be whitelisted in
-   * the "Allowed Callback URLs" field in your Auth0 Application's
-   * settings. If not provided here, it should be provided in the other
-   * methods that provide authentication.
-   */
-  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.
@@ -272,7 +281,7 @@ export type CacheLocation = 'memory' | 'localstorage';
 /**
  * @ignore
  */
-export interface AuthorizeOptions extends BaseLoginOptions {
+export interface AuthorizeOptions extends AuthorizationParams {
   response_type: string;
   response_mode: string;
   redirect_uri: string;
@@ -285,13 +294,6 @@ export interface AuthorizeOptions extends BaseLoginOptions {
 
 export interface RedirectLoginOptions<TAppState = any>
   extends BaseLoginOptions {
-  /**
-   * The URL where Auth0 will redirect your browser to with
-   * the authentication result. It must be whitelisted in
-   * the "Allowed Callback URLs" field in your Auth0 Application's
-   * settings.
-   */
-  redirect_uri?: string;
   /**
    * Used to store state before doing the redirect
    */
@@ -369,24 +371,35 @@ export interface GetTokenSilentlyOptions {
   cacheMode?: 'on' | 'off' | 'cache-only';
 
   /**
-   * There's no actual redirect when getting a token silently,
-   * but, according to the spec, a `redirect_uri` param is required.
-   * Auth0 uses this parameter to validate that the current `origin`
-   * matches the `redirect_uri` `origin` when sending the response.
-   * It must be whitelisted in the "Allowed Web Origins" in your
-   * Auth0 Application's settings.
-   */
-  redirect_uri?: string;
-
-  /**
-   * The scope that was used in the authentication request
-   */
-  scope?: string;
-
-  /**
-   * The audience that was used in the authentication request
-   */
-  audience?: string;
+   * Parameters that will be sent back to Auth0 as part of a request.
+   */
+  authorizationParams?: {
+    /**
+     * There's no actual redirect when getting a token silently,
+     * but, according to the spec, a `redirect_uri` param is required.
+     * Auth0 uses this parameter to validate that the current `origin`
+     * matches the `redirect_uri` `origin` when sending the response.
+     * It must be whitelisted in the "Allowed Web Origins" in your
+     * Auth0 Application's settings.
+     */
+    redirect_uri?: string;
+
+    /**
+     * The scope that was used in the authentication request
+     */
+    scope?: string;
+
+    /**
+     * The audience that was used in the authentication request
+     */
+    audience?: string;
+
+    /**
+     * If you need to send custom parameters to the Authorization Server,
+     * make sure to use the original parameter name.
+     */
+    [key: string]: any;
+  };
 
   /** A maximum number of seconds to wait before declaring the background /authorize call as failed for timeout
    * Defaults to 60s.
@@ -400,12 +413,6 @@ export interface GetTokenSilentlyOptions {
    * The default is `false`.
    */
   detailedResponse?: boolean;
-
-  /**
-   * If you need to send custom parameters to the Authorization Server,
-   * make sure to use the original parameter name.
-   */
-  [key: string]: any;
 }
 
 export interface GetTokenWithPopupOptions extends PopupLoginOptions {
@@ -418,20 +425,6 @@ export interface GetTokenWithPopupOptions extends PopupLoginOptions {
 }
 
 export interface LogoutUrlOptions {
-  /**
-   * The URL where Auth0 will redirect your browser to after the logout.
-   *
-   * **Note**: If the `clientId` parameter is included, the
-   * `returnTo` URL that is provided must be listed in the
-   * Application's "Allowed Logout URLs" in the Auth0 dashboard.
-   * However, if the `clientId` parameter is not included, the
-   * `returnTo` URL must be listed in the "Allowed Logout URLs" at
-   * the account level in the Auth0 dashboard.
-   *
-   * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
-   */
-  returnTo?: string;
-
   /**
    * The `clientId` of your application.
    *
@@ -441,52 +434,41 @@ export interface LogoutUrlOptions {
    *
    * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
    */
-   clientId?: string;
-
-  /**
-   * When supported by the upstream identity provider,
-   * forces the user to logout of their identity provider
-   * and from Auth0.
-   * [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps)
-   */
-  federated?: boolean;
+  clientId?: string;
+  /**
+   * Parameters to pass to the logout endpoint. This can be known parameters defined by Auth0 or custom parameters
+   * you wish to provide.
+   */
+  logoutParams?: {
+    /**
+     * When supported by the upstream identity provider,
+     * forces the user to logout of their identity provider
+     * and from Auth0.
+     * [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps)
+     */
+    federated?: boolean;
+    /**
+     * The URL where Auth0 will redirect your browser to after the logout.
+     *
+     * **Note**: If the `client_id` parameter is included, the
+     * `returnTo` URL that is provided must be listed in the
+     * Application's "Allowed Logout URLs" in the Auth0 dashboard.
+     * However, if the `client_id` parameter is not included, the
+     * `returnTo` URL must be listed in the "Allowed Logout URLs" at
+     * the account level in the Auth0 dashboard.
+     *
+     * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
+     */
+    returnTo?: string;
+
+    /**
+     * If you need to send custom parameters to the logout endpoint, make sure to use the original parameter name.
+     */
+    [key: string]: any;
+  };
 }
 
-export interface LogoutOptions {
-  /**
-   * The URL where Auth0 will redirect your browser to after the logout.
-   *
-   * **Note**: If the `clientId` parameter is included, the
-   * `returnTo` URL that is provided must be listed in the
-   * Application's "Allowed Logout URLs" in the Auth0 dashboard.
-   * However, if the `clientId` parameter is not included, the
-   * `returnTo` URL must be listed in the "Allowed Logout URLs" at
-   * the account level in the Auth0 dashboard.
-   *
-   * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
-   */
-  returnTo?: string;
-
-  /**
-   * The `clientId` of your application.
-   *
-   * If this property is not set, then the `clientId` that was used during initialization of the SDK is sent to the logout endpoint.
-   *
-   * If this property is set to `null`, then no client ID value is sent to the logout endpoint.
-   *
-   * [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
-   */
-   clientId?: string;
-
-  /**
-   * When supported by the upstream identity provider,
-   * forces the user to logout of their identity provider
-   * and from Auth0.
-   * This option cannot be specified along with the `localOnly` option.
-   * [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps)
-   */
-  federated?: boolean;
-
+export interface LogoutOptions extends LogoutUrlOptions {
   /**
    * When `true`, this skips the request to the logout endpoint on the authorization server,
    * effectively performing a "local" logout of the application. No redirect should take place,
diff --git a/src/utils.ts b/src/utils.ts
index fa3df1f..1bd6b39 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -165,7 +165,9 @@ const stripUndefined = (params: any) => {
 };
 
 export const createQueryParams = ({ clientId: client_id, ...params }: any) => {
-  return new URLSearchParams(stripUndefined({ client_id, ...params })).toString();
+  return new URLSearchParams(
+    stripUndefined({ client_id, ...params })
+  ).toString();
 };
 
 export const sha256 = async (s: string) => {
diff --git a/static/index.html b/static/index.html
index 8e6a1a5..29b35ed 100644
--- a/static/index.html
+++ b/static/index.html
@@ -526,17 +526,20 @@
               cacheLocation: _self.useLocalStorage ? 'localstorage' : 'memory',
               useRefreshTokens: _self.useRefreshTokens,
               useCookiesForTransactions: _self.useCookiesForTransactions,
-              redirect_uri: window.location.origin,
               useFormData: _self.useFormData,
-              useRefreshTokensFallback: _self.useRefreshTokensFallback
+              useRefreshTokensFallback: _self.useRefreshTokensFallback,
+              authorizationParams: {
+                redirect_uri: window.location.origin
+              }
             };
 
             if (_self.audience) {
-              clientOptions.audience = _self.audience;
+              clientOptions.authorizationParams.audience = _self.audience;
             }
 
             if (_self.organization && !_self.useOrgAtLogin) {
-              clientOptions.organization = _self.organization;
+              clientOptions.authorizationParams.organization =
+                _self.organization;
             }
 
             if (_self.useCustomStorage) {
@@ -577,6 +580,10 @@
             localStorage.setItem(
               'spa-playground-data',
               JSON.stringify({
+                authorizationParams: {
+                  audience: this.audience,
+                  organization: this.organization
+                },
                 domain: this.domain,
                 clientId: this.clientId,
                 useLocalStorage: this.useLocalStorage,
@@ -585,8 +592,6 @@
                 useConstructor: this.useConstructor,
                 useCookiesForTransactions: this.useCookiesForTransactions,
                 useCache: this.useCache,
-                audience: this.audience,
-                organization: this.organization,
                 useFormData: this.useFormData,
                 useOrgAtLogin: this.useOrgAtLogin,
                 useRefreshTokensFallback: this.useRefreshTokensFallback
@@ -608,7 +613,7 @@
             this.organization = defaultOrganization;
             this.useFormData = true;
             this.useOrgAtLogin = false;
-            this.useRefreshTokensFallback = false
+            this.useRefreshTokensFallback = false;
             this.saveForm();
           },
           showAuth0Info: function () {
@@ -664,11 +669,13 @@
             var _self = this;
 
             var options = {
-              redirect_uri: window.location.origin + '/callback.html'
+              authorizationParams: {
+                redirect_uri: window.location.origin + '/callback.html'
+              }
             };
 
             if (_self.organization) {
-              options.organization = _self.organization;
+              options.authorizationParams.organization = _self.organization;
             }
 
             _self.auth0
@@ -685,11 +692,15 @@
           },
           loginRedirect: function () {
             var _self = this;
-            var options = { scope: _self.audienceScopes[0].scope };
+            var options = {
+              authorizationParams: {
+                scope: _self.audienceScopes[0].scope
+              }
+            };
 
             if (_self.organization) {
               if (_self.useOrgAtLogin) {
-                options.organization = _self.organization;
+                options.authorizationParams.organization = _self.organization;
               }
             }
 
@@ -716,8 +727,10 @@
 
             _self.auth0
               .getTokenSilently({
-                audience: audience,
-                scope: scope,
+                authorizationParams: {
+                  audience: audience,
+                  scope: scope
+                },
                 cacheMode: _self.useCache ? 'on' : 'off'
               })
               .then(function (token) {
@@ -746,7 +759,12 @@
             var _self = this;
 
             _self.auth0
-              .getTokenWithPopup({ audience: audience, scope: scope })
+              .getTokenWithPopup({
+                authorizationParams: {
+                  audience: audience,
+                  scope: scope
+                }
+              })
               .then(function (token) {
                 access_tokens.push(token);
               })
@@ -756,13 +774,17 @@
           },
           logout: function () {
             this.auth0.logout({
-              returnTo: window.location.origin
+              logoutParams: {
+                returnTo: window.location.origin
+              }
             });
           },
           logoutNoClient: function () {
             this.auth0.logout({
               clientId: null,
-              returnTo: window.location.origin
+              logoutParams: {
+                returnTo: window.location.origin
+              }
             });
           },
           loginHandleInvitationUrl: function () {
@@ -776,8 +798,10 @@
 
                 if (orgMatches) {
                   this.auth0.loginWithRedirect({
-                    organization: orgMatches[1],
-                    invitation: inviteMatches[1]
+                    authorizationParams: {
+                      organization: orgMatches[1],
+                      invitation: inviteMatches[1]
+                    }
                   });
                 }
               }
diff --git a/static/multiple_clients.html b/static/multiple_clients.html
index f3c9a2e..48d3b9d 100644
--- a/static/multiple_clients.html
+++ b/static/multiple_clients.html
@@ -81,7 +81,9 @@
           const useAuth0 = false;
 
           const commonOptions = {
-            redirect_uri: `${window.location.origin}/multiple_clients.html`,
+            authorizationParams: {
+              redirect_uri: `${window.location.origin}/multiple_clients.html`
+            },
             cacheLocation: 'localstorage',
             useFormData: true,
             domain: useAuth0 ? 'brucke.auth0.com' : window.location.origin,


Test patch:
diff --git a/__tests__/Auth0Client/buildLogoutUrl.test.ts b/__tests__/Auth0Client/buildLogoutUrl.test.ts
index 07a1732..d55fe1d 100644
--- a/__tests__/Auth0Client/buildLogoutUrl.test.ts
+++ b/__tests__/Auth0Client/buildLogoutUrl.test.ts
@@ -98,8 +98,10 @@ describe('Auth0Client', () => {
       const auth0 = setup();
 
       const url = auth0.buildLogoutUrl({
-        returnTo: 'https://return.to',
-        clientId: null
+        clientId: null,
+        logoutParams: {
+          returnTo: 'https://return.to'
+        }
       });
 
       assertUrlEquals(url, TEST_DOMAIN, '/v2/logout', {
@@ -110,7 +112,12 @@ describe('Auth0Client', () => {
     it('creates correct query params when `options.federated` is true', async () => {
       const auth0 = setup();
 
-      const url = auth0.buildLogoutUrl({ federated: true, clientId: null });
+      const url = auth0.buildLogoutUrl({
+        logoutParams: {
+          federated: true
+        },
+        clientId: null
+      });
 
       assertUrlEquals(url, TEST_DOMAIN, '/v2/logout', {
         federated: ''
diff --git a/__tests__/Auth0Client/constructor.test.ts b/__tests__/Auth0Client/constructor.test.ts
index 4197140..ac6b3e2 100644
--- a/__tests__/Auth0Client/constructor.test.ts
+++ b/__tests__/Auth0Client/constructor.test.ts
@@ -77,7 +77,9 @@ describe('Auth0Client', () => {
     it('automatically adds the offline_access scope during construction', () => {
       const auth0 = setup({
         useRefreshTokens: true,
-        scope: 'test-scope'
+        authorizationParams: {
+          scope: 'test-scope'
+        }
       });
 
       expect((<any>auth0).scope).toBe('test-scope offline_access');
@@ -197,7 +199,9 @@ describe('Auth0Client', () => {
       const auth0 = setup();
 
       const url = auth0.buildLogoutUrl({
-        returnTo: 'https://return.to',
+        logoutParams: {
+          returnTo: 'https://return.to'
+        },
         clientId: null
       });
 
@@ -209,7 +213,12 @@ describe('Auth0Client', () => {
     it('creates correct query params when `options.federated` is true', async () => {
       const auth0 = setup();
 
-      const url = auth0.buildLogoutUrl({ federated: true, clientId: null });
+      const url = auth0.buildLogoutUrl({
+        logoutParams: {
+          federated: true
+        },
+        clientId: null
+      });
 
       assertUrlEquals(url, TEST_DOMAIN, '/v2/logout', {
         federated: ''
diff --git a/__tests__/Auth0Client/getIdTokenClaims.test.ts b/__tests__/Auth0Client/getIdTokenClaims.test.ts
index d0b8742..70caf43 100644
--- a/__tests__/Auth0Client/getIdTokenClaims.test.ts
+++ b/__tests__/Auth0Client/getIdTokenClaims.test.ts
@@ -102,7 +102,7 @@ describe('Auth0Client', () => {
       }) => {
         describe(`when ${name}`, () => {
           it('returns the ID token claims', async () => {
-            const auth0 = setup({ scope: 'foo' });
+            const auth0 = setup({ authorizationParams: { scope: 'foo' } });
             await login(auth0);
 
             expect(await auth0.getIdTokenClaims()).toHaveProperty('exp');
@@ -121,12 +121,14 @@ describe('Auth0Client', () => {
 
           it('returns the ID token claims with custom scope', async () => {
             const auth0 = setup({
-              scope: 'scope1',
+              authorizationParams: {
+                scope: 'scope1'
+              },
               advancedOptions: {
                 defaultScope: 'scope2'
               }
             });
-            await login(auth0, { scope: 'scope3' });
+            await login(auth0, { authorizationParams: { scope: 'scope3' } });
 
             expect(
               await auth0.getIdTokenClaims({ scope: 'scope1 scope2 scope3' })
@@ -135,7 +137,10 @@ describe('Auth0Client', () => {
 
           describe('when using refresh tokens', () => {
             it('returns the ID token claims with offline_access', async () => {
-              const auth0 = setup({ scope: 'foo', useRefreshTokens: true });
+              const auth0 = setup({
+                authorizationParams: { scope: 'foo' },
+                useRefreshTokens: true
+              });
               await login(auth0);
 
               expect(
@@ -145,13 +150,15 @@ describe('Auth0Client', () => {
 
             it('returns the ID token claims with custom scope and offline_access', async () => {
               const auth0 = setup({
-                scope: 'scope1',
+                authorizationParams: {
+                  scope: 'scope1'
+                },
                 advancedOptions: {
                   defaultScope: 'scope2'
                 },
                 useRefreshTokens: true
               });
-              await login(auth0, { scope: 'scope3' });
+              await login(auth0, { authorizationParams: { scope: 'scope3' } });
 
               expect(
                 await auth0.getIdTokenClaims({
diff --git a/__tests__/Auth0Client/getTokenSilently.test.ts b/__tests__/Auth0Client/getTokenSilently.test.ts
index 5ca5f15..a8948d2 100644
--- a/__tests__/Auth0Client/getTokenSilently.test.ts
+++ b/__tests__/Auth0Client/getTokenSilently.test.ts
@@ -139,7 +139,9 @@ describe('Auth0Client', () => {
       });
 
       await getTokenSilently(auth0, {
-        foo: 'bar'
+        authorizationParams: {
+          foo: 'bar'
+        }
       });
 
       const [[url]] = (<jest.Mock>utils.runIframe).mock.calls;
@@ -162,7 +164,9 @@ describe('Auth0Client', () => {
     it('calls the authorize endpoint using the correct params when using a default redirect_uri', async () => {
       const redirect_uri = 'https://custom-redirect-uri/callback';
       const auth0 = setup({
-        redirect_uri
+        authorizationParams: {
+          redirect_uri
+        }
       });
 
       jest.spyOn(<any>utils, 'runIframe').mockResolvedValue({
@@ -313,7 +317,9 @@ describe('Auth0Client', () => {
       mockFetch.mockReset();
 
       await getTokenSilently(auth0, {
-        redirect_uri,
+        authorizationParams: {
+          redirect_uri
+        },
         cacheMode: 'off'
       });
 
@@ -343,7 +349,9 @@ describe('Auth0Client', () => {
       mockFetch.mockReset();
 
       await getTokenSilently(auth0, {
-        redirect_uri,
+        authorizationParams: {
+          redirect_uri
+        },
         cacheMode: 'off'
       });
 
@@ -366,8 +374,10 @@ describe('Auth0Client', () => {
     it('calls the token endpoint with the correct params when not providing any redirect uri, using refresh tokens and not using useFormData', async () => {
       const auth0 = setup({
         useRefreshTokens: true,
-        redirect_uri: null,
-        useFormData: false
+        useFormData: false,
+        authorizationParams: {
+          redirect_uri: null
+        }
       });
 
       await loginWithRedirect(auth0);
@@ -375,7 +385,9 @@ describe('Auth0Client', () => {
       mockFetch.mockReset();
 
       await getTokenSilently(auth0, {
-        redirect_uri: null,
+        authorizationParams: {
+          redirect_uri: null
+        },
         cacheMode: 'off'
       });
 
@@ -396,7 +408,9 @@ describe('Auth0Client', () => {
     it('calls the token endpoint with the correct params when not providing any redirect uri and using refresh tokens', async () => {
       const auth0 = setup({
         useRefreshTokens: true,
-        redirect_uri: null
+        authorizationParams: {
+          redirect_uri: null
+        }
       });
 
       await loginWithRedirect(auth0);
@@ -404,7 +418,9 @@ describe('Auth0Client', () => {
       mockFetch.mockReset();
 
       await getTokenSilently(auth0, {
-        redirect_uri: null,
+        authorizationParams: {
+          redirect_uri: null
+        },
         cacheMode: 'off'
       });
 
@@ -1375,15 +1391,19 @@ describe('Auth0Client', () => {
         })
       );
       let access_token = await auth0.getTokenSilently({
-        audience: 'foo',
-        scope: 'bar'
+        authorizationParams: {
+          audience: 'foo',
+          scope: 'bar'
+        }
       });
       expect(access_token).toEqual(TEST_ACCESS_TOKEN);
       expect(utils.runIframe).toHaveBeenCalledTimes(1);
       (<jest.Mock>utils.runIframe).mockClear();
       access_token = await auth0.getTokenSilently({
-        audience: 'foo',
-        scope: 'bar'
+        authorizationParams: {
+          audience: 'foo',
+          scope: 'bar'
+        }
       });
       expect(access_token).toEqual(TEST_ACCESS_TOKEN);
       expect(utils.runIframe).not.toHaveBeenCalled();
@@ -1404,12 +1424,16 @@ describe('Auth0Client', () => {
           expires_in: 86400
         })
       );
-      let access_token = await auth0.getTokenSilently({ audience: 'foo' });
+      let access_token = await auth0.getTokenSilently({
+        authorizationParams: { audience: 'foo' }
+      });
       expect(access_token).toEqual(TEST_ACCESS_TOKEN);
       expect(acquireLockSpy).toHaveBeenCalled();
       acquireLockSpy.mockClear();
       // This request will hit the cache, so should not acquire the lock
-      access_token = await auth0.getTokenSilently({ audience: 'foo' });
+      access_token = await auth0.getTokenSilently({
+        authorizationParams: { audience: 'foo' }
+      });
       expect(access_token).toEqual(TEST_ACCESS_TOKEN);
       expect(acquireLockSpy).not.toHaveBeenCalled();
     });
@@ -1502,8 +1526,10 @@ describe('Auth0Client', () => {
 
     it('sends custom options through to the token endpoint when using an iframe when not using useFormData', async () => {
       const auth0 = setup({
-        custom_param: 'foo',
-        another_custom_param: 'bar',
+        authorizationParams: {
+          custom_param: 'foo',
+          another_custom_param: 'bar'
+        },
         useFormData: false
       });
 
@@ -1525,7 +1551,9 @@ describe('Auth0Client', () => {
 
       await auth0.getTokenSilently({
         cacheMode: 'off',
-        custom_param: 'hello world'
+        authorizationParams: {
+          custom_param: 'hello world'
+        }
       });
 
       expect(
@@ -1546,8 +1574,10 @@ describe('Auth0Client', () => {
 
     it('sends custom options through to the token endpoint when using an iframe', async () => {
       const auth0 = setup({
-        custom_param: 'foo',
-        another_custom_param: 'bar'
+        authorizationParams: {
+          custom_param: 'foo',
+          another_custom_param: 'bar'
+        }
       });
 
       await loginWithRedirect(auth0);
@@ -1568,7 +1598,9 @@ describe('Auth0Client', () => {
 
       await auth0.getTokenSilently({
         cacheMode: 'off',
-        custom_param: 'hello world'
+        authorizationParams: {
+          custom_param: 'hello world'
+        }
       });
 
       expect(
@@ -1597,9 +1629,11 @@ describe('Auth0Client', () => {
 
     it('sends custom options through to the token endpoint when using refresh tokens when not using useFormData', async () => {
       const auth0 = setup({
+        authorizationParams: {
+          custom_param: 'foo',
+          another_custom_param: 'bar'
+        },
         useRefreshTokens: true,
-        custom_param: 'foo',
-        another_custom_param: 'bar',
         useFormData: false
       });
 
@@ -1627,7 +1661,9 @@ describe('Auth0Client', () => {
 
       const access_token = await auth0.getTokenSilently({
         cacheMode: 'off',
-        custom_param: 'hello world'
+        authorizationParams: {
+          custom_param: 'hello world'
+        }
       });
 
       expect(JSON.parse(mockFetch.mock.calls[1][1].body)).toEqual({
@@ -1645,8 +1681,10 @@ describe('Auth0Client', () => {
     it('sends custom options through to the token endpoint when using refresh tokens', async () => {
       const auth0 = setup({
         useRefreshTokens: true,
-        custom_param: 'foo',
-        another_custom_param: 'bar'
+        authorizationParams: {
+          custom_param: 'foo',
+          another_custom_param: 'bar'
+        }
       });
 
       await loginWithRedirect(auth0, undefined, {
@@ -1673,7 +1711,9 @@ describe('Auth0Client', () => {
 
       const access_token = await auth0.getTokenSilently({
         cacheMode: 'off',
-        custom_param: 'helloworld'
+        authorizationParams: {
+          custom_param: 'helloworld'
+        }
       });
 
       assertPost(
@@ -1872,7 +1912,9 @@ describe('Auth0Client', () => {
     it('opens iframe with correct urls including organization from the options', async () => {
       const auth0 = setup({
         authorizeTimeoutInSeconds: 1,
-        organization: TEST_ORG_ID
+        authorizationParams: {
+          organization: TEST_ORG_ID
+        }
       });
 
       jest.spyOn(<any>utils, 'runIframe').mockResolvedValue({
@@ -1915,7 +1957,9 @@ describe('Auth0Client', () => {
     it('opens iframe with correct urls including organization, with options taking precedence over hint cookie', async () => {
       const auth0 = setup({
         authorizeTimeoutInSeconds: 1,
-        organization: 'another_test_org'
+        authorizationParams: {
+          organization: 'another_test_org'
+        }
       });
 
       jest.spyOn(<any>utils, 'runIframe').mockResolvedValue({
@@ -2180,7 +2224,9 @@ describe('Auth0Client', () => {
 
     it('returns the full response with scopes when "detailedResponse: true" and using cache', async () => {
       const auth0 = setup({
-        scope: 'read:messages write:messages'
+        authorizationParams: {
+          scope: 'read:messages write:messages'
+        }
       });
 
       const runIframeSpy = jest
@@ -2206,7 +2252,9 @@ describe('Auth0Client', () => {
 
       await auth0.getTokenSilently({
         cacheMode: 'off',
-        scope: 'read:messages'
+        authorizationParams: {
+          scope: 'read:messages'
+        }
       });
 
       expect(auth0['cacheManager'].set).toHaveBeenCalledWith(
@@ -2224,7 +2272,9 @@ describe('Auth0Client', () => {
       // oauthTokenScope in the scope property
       const response = await auth0.getTokenSilently({
         detailedResponse: true,
-        scope: 'read:messages'
+        authorizationParams: {
+          scope: 'read:messages'
+        }
       });
 
       // No refresh_token included here, or oauthTokenScope
diff --git a/__tests__/Auth0Client/getTokenWithPopup.test.ts b/__tests__/Auth0Client/getTokenWithPopup.test.ts
index 4656430..8aed4db 100644
--- a/__tests__/Auth0Client/getTokenWithPopup.test.ts
+++ b/__tests__/Auth0Client/getTokenWithPopup.test.ts
@@ -113,7 +113,9 @@ describe('Auth0Client', () => {
         advancedOptions: {
           defaultScope: 'email'
         },
-        scope: 'read:email'
+        authorizationParams: {
+          scope: 'read:email'
+        }
       });
 
       const config = {
@@ -136,8 +138,10 @@ describe('Auth0Client', () => {
       const auth0 = await localSetup();
 
       const loginOptions = {
-        audience: 'other-audience',
-        screen_hint: 'signup'
+        authorizationParams: {
+          audience: 'other-audience',
+          screen_hint: 'signup'
+        }
       };
 
       const config = {
@@ -159,8 +163,10 @@ describe('Auth0Client', () => {
       const auth0 = await localSetup({});
 
       const loginOptions = {
-        audience: 'other-audience',
-        screen_hint: 'signup'
+        authorizationParams: {
+          audience: 'other-audience',
+          screen_hint: 'signup'
+        }
       };
 
       const config = {
@@ -193,7 +199,9 @@ describe('Auth0Client', () => {
 
     it('can use the global audience', async () => {
       const auth0 = await localSetup({
-        audience: 'global-audience'
+        authorizationParams: {
+          audience: 'global-audience'
+        }
       });
 
       const config = {
diff --git a/__tests__/Auth0Client/handleRedirectCallback.test.ts b/__tests__/Auth0Client/handleRedirectCallback.test.ts
index 190a81d..bb2c756 100644
--- a/__tests__/Auth0Client/handleRedirectCallback.test.ts
+++ b/__tests__/Auth0Client/handleRedirectCallback.test.ts
@@ -329,7 +329,7 @@ describe('Auth0Client', () => {
     const auth0 = setup({
       useFormData: false
     });
-    delete auth0['options']['redirect_uri'];
+    delete auth0['options']['authorizationParams']?.['redirect_uri'];
 
     await loginWithRedirect(auth0);
 
@@ -356,7 +356,7 @@ describe('Auth0Client', () => {
     );
 
     const auth0 = setup();
-    delete auth0['options']['redirect_uri'];
+    delete auth0['options']['authorizationParams']?.['redirect_uri'];
 
     await loginWithRedirect(auth0);
 
diff --git a/__tests__/Auth0Client/helpers.ts b/__tests__/Auth0Client/helpers.ts
index 669240e..eb997a1 100644
--- a/__tests__/Auth0Client/helpers.ts
+++ b/__tests__/Auth0Client/helpers.ts
@@ -106,16 +106,21 @@ export const fetchResponse = (ok, json) =>
 
 export const setupFn = (mockVerify: jest.Mock) => {
   return (config?: Partial<Auth0ClientOptions>, claims?: Partial<IdToken>) => {
-    const auth0 = new Auth0Client(
-      Object.assign(
-        {
-          domain: TEST_DOMAIN,
-          clientId: TEST_CLIENT_ID,
-          redirect_uri: TEST_REDIRECT_URI
-        },
-        config
-      )
-    );
+    const options: Auth0ClientOptions = {
+      domain: TEST_DOMAIN,
+      clientId: TEST_CLIENT_ID,
+      authorizationParams: {
+        redirect_uri: TEST_REDIRECT_URI
+      }
+    };
+
+    Object.assign(options.authorizationParams, config?.authorizationParams);
+
+    delete config?.authorizationParams;
+
+    Object.assign(options, config);
+
+    const auth0 = new Auth0Client(options);
 
     mockVerify.mockReturnValue({
       claims: Object.assign(
diff --git a/__tests__/Auth0Client/loginWithPopup.test.ts b/__tests__/Auth0Client/loginWithPopup.test.ts
index c1c157f..4a65589 100644
--- a/__tests__/Auth0Client/loginWithPopup.test.ts
+++ b/__tests__/Auth0Client/loginWithPopup.test.ts
@@ -99,7 +99,7 @@ describe('Auth0Client', () => {
 
   describe('loginWithPopup', () => {
     it('should log the user in and get the user and claims', async () => {
-      const auth0 = setup({ scope: 'foo' });
+      const auth0 = setup({ authorizationParams: { scope: 'foo' } });
 
       mockWindow.open.mockReturnValue({ hello: 'world' });
 
@@ -127,12 +127,14 @@ describe('Auth0Client', () => {
 
     it('should log the user in with custom scope', async () => {
       const auth0 = setup({
-        scope: 'scope1',
+        authorizationParams: {
+          scope: 'scope1'
+        },
         advancedOptions: {
           defaultScope: 'scope2'
         }
       });
-      await loginWithPopup(auth0, { scope: 'scope3' });
+      await loginWithPopup(auth0, { authorizationParams: { scope: 'scope3' } });
 
       const expectedUser = { sub: 'me' };
 
@@ -165,8 +167,10 @@ describe('Auth0Client', () => {
       const auth0 = setup({ leeway: 10, httpTimeoutInSeconds: 60 });
 
       await loginWithPopup(auth0, {
-        connection: 'test-connection',
-        audience: 'test'
+        authorizationParams: {
+          connection: 'test-connection',
+          audience: 'test'
+        }
       });
 
       expect(mockWindow.open).toHaveBeenCalled();
@@ -212,11 +216,16 @@ describe('Auth0Client', () => {
     });
 
     it('should log the user in with a popup and redirect using a default redirect URI', async () => {
-      const auth0 = setup({ leeway: 10, redirect_uri: null });
+      const auth0 = setup({
+        leeway: 10,
+        authorizationParams: { redirect_uri: undefined }
+      });
 
       await loginWithPopup(auth0, {
-        connection: 'test-connection',
-        audience: 'test'
+        authorizationParams: {
+          connection: 'test-connection',
+          audience: 'test'
+        }
       });
 
       expect(mockWindow.open).toHaveBeenCalled();
@@ -243,8 +252,10 @@ describe('Auth0Client', () => {
       const auth0 = setup({ leeway: 10 });
 
       await loginWithPopup(auth0, {
-        connection: 'test-connection',
-        audience: 'test'
+        authorizationParams: {
+          connection: 'test-connection',
+          audience: 'test'
+        }
       });
 
       expect(mockWindow.open).toHaveBeenCalled();
@@ -291,7 +302,9 @@ describe('Auth0Client', () => {
     it('should log the user and redirect when using different default redirect_uri', async () => {
       const redirect_uri = 'https://custom-redirect-uri/callback';
       const auth0 = setup({
-        redirect_uri
+        authorizationParams: {
+          redirect_uri
+        }
       });
       await loginWithPopup(auth0);
 
@@ -406,7 +419,12 @@ describe('Auth0Client', () => {
 
       await loginWithPopup(
         auth0,
-        { connection: 'test-connection', audience: 'test' },
+        {
+          authorizationParams: {
+            connection: 'test-connection',
+            audience: 'test'
+          }
+        },
         { popup }
       );
 
@@ -515,7 +533,7 @@ describe('Auth0Client', () => {
     });
 
     it('calls `tokenVerifier.verify` with undefined `max_age` when value set in constructor is an empty string', async () => {
-      const auth0 = setup({ max_age: '' });
+      const auth0 = setup({ authorizationParams: { max_age: '' } });
 
       await loginWithPopup(auth0);
 
@@ -527,7 +545,7 @@ describe('Auth0Client', () => {
     });
 
     it('calls `tokenVerifier.verify` with the parsed `max_age` string from constructor', async () => {
-      const auth0 = setup({ max_age: '10' });
+      const auth0 = setup({ authorizationParams: { max_age: '10' } });
 
       await loginWithPopup(auth0);
 
@@ -539,7 +557,7 @@ describe('Auth0Client', () => {
     });
 
     it('calls `tokenVerifier.verify` with the parsed `max_age` number from constructor', async () => {
-      const auth0 = setup({ max_age: 10 });
+      const auth0 = setup({ authorizationParams: { max_age: 10 } });
 
       await loginWithPopup(auth0);
 
@@ -551,7 +569,9 @@ describe('Auth0Client', () => {
     });
 
     it('calls `tokenVerifier.verify` with the organization id', async () => {
-      const auth0 = setup({ organization: 'test_org_123' });
+      const auth0 = setup({
+        authorizationParams: { organization: 'test_org_123' }
+      });
 
       await loginWithPopup(auth0);
 
@@ -564,7 +584,9 @@ describe('Auth0Client', () => {
 
     it('calls `tokenVerifier.verify` with the organization id given in the login method', async () => {
       const auth0 = setup();
-      await loginWithPopup(auth0, { organization: 'test_org_123' });
+      await loginWithPopup(auth0, {
+        authorizationParams: { organization: 'test_org_123' }
+      });
 
       expect(tokenVerifier).toHaveBeenCalledWith(
         expect.objectContaining({
@@ -740,5 +762,32 @@ describe('Auth0Client', () => {
         'HTTP error. Unable to fetch https://auth0_domain/oauth/token'
       );
     });
+
+    it('should log the user and redirect when using different redirect_uri on loginWithPopup', async () => {
+      const redirect_uri = 'https://custom-redirect-uri/callback';
+      const auth0 = setup({
+        authorizationParams: {
+          redirect_uri: 'https://redirect-uri-on-ctor/callback'
+        }
+      });
+      await loginWithPopup(auth0, {
+        authorizationParams: {
+          redirect_uri
+        }
+      });
+
+      // prettier-ignore
+      const url = (utils.runPopup as jest.Mock).mock.calls[0][0].popup.location.href;
+
+      assertUrlEquals(
+        url,
+        TEST_DOMAIN,
+        '/authorize',
+        {
+          redirect_uri
+        },
+        false
+      );
+    });
   });
 });
diff --git a/__tests__/Auth0Client/loginWithRedirect.test.ts b/__tests__/Auth0Client/loginWithRedirect.test.ts
index 6e7c0fd..b97c8f9 100644
--- a/__tests__/Auth0Client/loginWithRedirect.test.ts
+++ b/__tests__/Auth0Client/loginWithRedirect.test.ts
@@ -208,7 +208,9 @@ describe('Auth0Client', () => {
       const redirect_uri = 'https://custom-redirect-uri/callback';
 
       const auth0 = setup({
-        redirect_uri
+        authorizationParams: {
+          redirect_uri
+        }
       });
 
       await loginWithRedirect(auth0);
@@ -230,11 +232,15 @@ describe('Auth0Client', () => {
       const redirect_uri = 'https://custom-redirect-uri/callback';
 
       const auth0 = setup({
-        redirect_uri
+        authorizationParams: {
+          redirect_uri
+        }
       });
 
       await loginWithRedirect(auth0, {
-        redirect_uri: 'https://my-redirect-uri/callback'
+        authorizationParams: {
+          redirect_uri: 'https://my-redirect-uri/callback'
+        }
       });
 
       const url = new URL(mockWindow.location.assign.mock.calls[0][0]);
@@ -254,7 +260,9 @@ describe('Auth0Client', () => {
       const auth0 = setup();
 
       await loginWithRedirect(auth0, {
-        audience: 'test_audience',
+        authorizationParams: {
+          audience: 'test_audience'
+        },
         onRedirect: async url => window.location.replace(url)
       });
 
@@ -275,7 +283,9 @@ describe('Auth0Client', () => {
       const auth0 = setup();
 
       await loginWithRedirect(auth0, {
-        audience: 'test_audience'
+        authorizationParams: {
+          audience: 'test_audience'
+        }
       });
 
       const url = new URL(mockWindow.location.assign.mock.calls[0][0]);
@@ -312,7 +322,7 @@ describe('Auth0Client', () => {
     });
 
     it('should log the user in and get the user', async () => {
-      const auth0 = setup({ scope: 'foo' });
+      const auth0 = setup({ authorizationParams: { scope: 'foo' } });
       await loginWithRedirect(auth0);
 
       const expectedUser = { sub: 'me' };
@@ -328,13 +338,17 @@ describe('Auth0Client', () => {
 
     it('should log the user in and get the user with custom scope', async () => {
       const auth0 = setup({
-        scope: 'scope1',
+        authorizationParams: {
+          scope: 'scope1'
+        },
         advancedOptions: {
           defaultScope: 'scope2'
         }
       });
 
-      await loginWithRedirect(auth0, { scope: 'scope3' });
+      await loginWithRedirect(auth0, {
+        authorizationParams: { scope: 'scope3' }
+      });
 
       const expectedUser = { sub: 'me' };
 
@@ -412,7 +426,9 @@ describe('Auth0Client', () => {
     });
 
     it('calls `tokenVerifier.verify` with the global organization id', async () => {
-      const auth0 = setup({ organization: 'test_org_123' });
+      const auth0 = setup({
+        authorizationParams: { organization: 'test_org_123' }
+      });
 
       await loginWithRedirect(auth0);
 
@@ -460,10 +476,13 @@ describe('Auth0Client', () => {
     });
 
     it('calls `tokenVerifier.verify` with the specific organization id', async () => {
-      const auth0 = setup({ organization: 'test_org_123' });
-
-      await loginWithRedirect(auth0, { organization: 'test_org_456' });
+      const auth0 = setup({
+        authorizationParams: { organization: 'test_org_123' }
+      });
 
+      await loginWithRedirect(auth0, {
+        authorizationParams: { organization: 'test_org_456' }
+      });
       expect(tokenVerifier).toHaveBeenCalledWith(
         expect.objectContaining({
           organizationId: 'test_org_456'
diff --git a/__tests__/Auth0Client/logout.test.ts b/__tests__/Auth0Client/logout.test.ts
index ed1af34..44ce379 100644
--- a/__tests__/Auth0Client/logout.test.ts
+++ b/__tests__/Auth0Client/logout.test.ts
@@ -101,7 +101,7 @@ describe('Auth0Client', () => {
     it('calls `window.location.assign` with the correct url when `options.federated` is true', async () => {
       const auth0 = setup();
 
-      auth0.logout({ federated: true });
+      auth0.logout({ logoutParams: { federated: true } });
 
       expect(window.location.assign).toHaveBeenCalledWith(
         `https://${TEST_DOMAIN}/v2/logout?client_id=${TEST_CLIENT_ID}${TEST_AUTH0_CLIENT_QUERY_STRING}&federated`
@@ -170,7 +170,8 @@ describe('Auth0Client', () => {
     it('throws when both `options.localOnly` and `options.federated` are true', async () => {
       const auth0 = setup();
 
-      const fn = () => auth0.logout({ localOnly: true, federated: true });
+      const fn = () =>
+        auth0.logout({ localOnly: true, logoutParams: { federated: true } });
       expect(fn).toThrow();
     });
 
diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts
index 81aefc2..22c8dbd 100644
--- a/__tests__/index.test.ts
+++ b/__tests__/index.test.ts
@@ -280,8 +280,10 @@ describe('Auth0', () => {
         const { cookieStorage } = await setup(null, false);
 
         const options = {
-          audience: 'the-audience',
-          scope: 'the-scope',
+          authorizationParams: {
+            audience: 'the-audience',
+            scope: 'the-scope'
+          },
           useRefreshTokens: true
         };