From 30c100e3fdaa00be4d627f84351e3421d9829243 Mon Sep 17 00:00:00 2001
From: Adam Mcgrath <adamjmcgrath@gmail.com>
Date: Wed, 1 Nov 2023 16:06:45 +0000
Subject: [PATCH 1/7] Allow lazy config in auth0-session

---
 src/auth0-session/client/abstract-client.ts   |  7 +-
 src/auth0-session/client/edge-client.ts       | 90 +++++++++----------
 src/auth0-session/client/node-client.ts       |  8 +-
 src/auth0-session/config.ts                   |  2 +
 src/auth0-session/handlers/callback.ts        |  6 +-
 src/auth0-session/handlers/login.ts           |  6 +-
 src/auth0-session/handlers/logout.ts          |  6 +-
 src/auth0-session/session/abstract-session.ts | 22 +++--
 src/auth0-session/session/stateful-session.ts | 35 +++++---
 .../session/stateless-session.ts              | 50 +++++++----
 src/auth0-session/transient-store.ts          | 21 +++--
 .../auth0-session/client/edge-client.test.ts  |  3 +-
 .../auth0-session/client/node-client.test.ts  | 15 ++++
 tests/auth0-session/handlers/callback.test.ts |  8 ++
 tests/auth0-session/handlers/login.test.ts    |  8 ++
 tests/auth0-session/handlers/logout.test.ts   |  8 ++
 .../session/stateless-session.test.ts         | 10 +++
 tests/auth0-session/transient-store.test.ts   |  9 ++
 18 files changed, 206 insertions(+), 108 deletions(-)

diff --git a/src/auth0-session/client/abstract-client.ts b/src/auth0-session/client/abstract-client.ts
index d51771e73..25f0c9360 100644
--- a/src/auth0-session/client/abstract-client.ts
+++ b/src/auth0-session/client/abstract-client.ts
@@ -1,4 +1,4 @@
-import { Config } from '../config';
+import { Config, GetConfig } from '../config';
 import { Auth0Request } from '../http';
 
 export type Telemetry = {
@@ -85,7 +85,10 @@ export interface AuthorizationParameters {
 }
 
 export abstract class AbstractClient {
-  constructor(protected config: Config, protected telemetry: Telemetry) {}
+  protected getConfig: () => Config | Promise<Config>;
+  constructor(getConfig: GetConfig, protected telemetry: Telemetry) {
+    this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
+  }
   abstract authorizationUrl(parameters: Record<string, unknown>): Promise<string>;
   abstract callbackParams(req: Auth0Request, expectedState: string): Promise<URLSearchParams>;
   abstract callback(
diff --git a/src/auth0-session/client/edge-client.ts b/src/auth0-session/client/edge-client.ts
index 0529d1c69..1480f7bca 100644
--- a/src/auth0-session/client/edge-client.ts
+++ b/src/auth0-session/client/edge-client.ts
@@ -6,13 +6,11 @@ import {
   OpenIDCallbackChecks,
   TokenEndpointResponse,
   AbstractClient,
-  EndSessionParameters,
-  Telemetry
+  EndSessionParameters
 } from './abstract-client';
 import { ApplicationError, DiscoveryError, IdentityProviderError, UserInfoError } from '../utils/errors';
 import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors';
 import urlJoin from 'url-join';
-import { Config } from '../config';
 
 const encodeBase64 = (input: string) => {
   const unencoded = new TextEncoder().encode(input);
@@ -28,36 +26,29 @@ const encodeBase64 = (input: string) => {
 export class EdgeClient extends AbstractClient {
   private client?: oauth.Client;
   private as?: oauth.AuthorizationServer;
-  private httpOptions: () => oauth.HttpRequestOptions;
 
-  constructor(protected config: Config, protected telemetry: Telemetry) {
-    super(config, telemetry);
-    if (config.authorizationParams.response_type !== 'code') {
-      throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
+  private async httpOptions(): Promise<oauth.HttpRequestOptions> {
+    const headers = new Headers();
+    const config = await this.getConfig();
+    if (config.enableTelemetry) {
+      const { name, version } = this.telemetry;
+      headers.set('User-Agent', `${name}/${version}`);
+      headers.set(
+        'Auth0-Client',
+        encodeBase64(
+          JSON.stringify({
+            name,
+            version,
+            env: {
+              edge: true
+            }
+          })
+        )
+      );
     }
-
-    this.httpOptions = () => {
-      const headers = new Headers();
-      if (config.enableTelemetry) {
-        const { name, version } = telemetry;
-        headers.set('User-Agent', `${name}/${version}`);
-        headers.set(
-          'Auth0-Client',
-          encodeBase64(
-            JSON.stringify({
-              name,
-              version,
-              env: {
-                edge: true
-              }
-            })
-          )
-        );
-      }
-      return {
-        signal: AbortSignal.timeout(this.config.httpTimeout),
-        headers
-      };
+    return {
+      signal: AbortSignal.timeout(config.httpTimeout),
+      headers
     };
   }
 
@@ -65,22 +56,26 @@ export class EdgeClient extends AbstractClient {
     if (this.as) {
       return [this.as, this.client as oauth.Client];
     }
+    const config = await this.getConfig();
+    if (config.authorizationParams.response_type !== 'code') {
+      throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
+    }
 
-    const issuer = new URL(this.config.issuerBaseURL);
+    const issuer = new URL(config.issuerBaseURL);
     try {
       this.as = await oauth
-        .discoveryRequest(issuer, this.httpOptions())
+        .discoveryRequest(issuer, await this.httpOptions())
         .then((response) => oauth.processDiscoveryResponse(issuer, response));
     } catch (e) {
-      throw new DiscoveryError(e, this.config.issuerBaseURL);
+      throw new DiscoveryError(e, config.issuerBaseURL);
     }
 
     this.client = {
-      client_id: this.config.clientID,
-      ...(!this.config.clientAssertionSigningKey && { client_secret: this.config.clientSecret }),
-      token_endpoint_auth_method: this.config.clientAuthMethod,
-      id_token_signed_response_alg: this.config.idTokenSigningAlg,
-      [oauth.clockTolerance]: this.config.clockTolerance
+      client_id: config.clientID,
+      ...(!config.clientAssertionSigningKey && { client_secret: config.clientSecret }),
+      token_endpoint_auth_method: config.clientAuthMethod,
+      id_token_signed_response_alg: config.idTokenSigningAlg,
+      [oauth.clockTolerance]: config.clockTolerance
     };
 
     return [this.as, this.client];
@@ -88,8 +83,9 @@ export class EdgeClient extends AbstractClient {
 
   async authorizationUrl(parameters: Record<string, unknown>): Promise<string> {
     const [as] = await this.getClient();
+    const config = await this.getConfig();
     const authorizationUrl = new URL(as.authorization_endpoint as string);
-    authorizationUrl.searchParams.set('client_id', this.config.clientID);
+    authorizationUrl.searchParams.set('client_id', config.clientID);
     Object.entries(parameters).forEach(([key, value]) => {
       if (value === null || value === undefined) {
         return;
@@ -126,8 +122,7 @@ export class EdgeClient extends AbstractClient {
     extras: CallbackExtras
   ): Promise<TokenEndpointResponse> {
     const [as, client] = await this.getClient();
-
-    const { clientAssertionSigningKey, clientAssertionSigningAlg } = this.config;
+    const { clientAssertionSigningKey, clientAssertionSigningAlg } = await this.getConfig();
 
     let clientPrivateKey = clientAssertionSigningKey as CryptoKey | undefined;
     /* c8 ignore next 3 */
@@ -167,15 +162,16 @@ export class EdgeClient extends AbstractClient {
   async endSessionUrl(parameters: EndSessionParameters): Promise<string> {
     const [as] = await this.getClient();
     const issuerUrl = new URL(as.issuer);
+    const config = await this.getConfig();
 
     if (
-      this.config.idpLogout &&
-      (this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
+      config.idpLogout &&
+      (config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false))
     ) {
       const { id_token_hint, post_logout_redirect_uri, ...extraParams } = parameters;
       const auth0LogoutUrl: URL = new URL(urlJoin(as.issuer, '/v2/logout'));
       post_logout_redirect_uri && auth0LogoutUrl.searchParams.set('returnTo', post_logout_redirect_uri);
-      auth0LogoutUrl.searchParams.set('client_id', this.config.clientID);
+      auth0LogoutUrl.searchParams.set('client_id', config.clientID);
       Object.entries(extraParams).forEach(([key, value]: [string, string]) => {
         if (value === null || value === undefined) {
           return;
@@ -195,13 +191,13 @@ export class EdgeClient extends AbstractClient {
       oidcLogoutUrl.searchParams.set(key, value);
     });
 
-    oidcLogoutUrl.searchParams.set('client_id', this.config.clientID);
+    oidcLogoutUrl.searchParams.set('client_id', config.clientID);
     return oidcLogoutUrl.toString();
   }
 
   async userinfo(accessToken: string): Promise<Record<string, unknown>> {
     const [as, client] = await this.getClient();
-    const response = await oauth.userInfoRequest(as, client, accessToken, this.httpOptions());
+    const response = await oauth.userInfoRequest(as, client, accessToken, await this.httpOptions());
 
     try {
       return await oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response);
diff --git a/src/auth0-session/client/node-client.ts b/src/auth0-session/client/node-client.ts
index dad56ac13..683f1f306 100644
--- a/src/auth0-session/client/node-client.ts
+++ b/src/auth0-session/client/node-client.ts
@@ -38,9 +38,10 @@ export class NodeClient extends AbstractClient {
       return this.client;
     }
     const {
-      config,
+      getConfig,
       telemetry: { name, version }
     } = this;
+    const config = await getConfig();
 
     const defaultHttpOptions: CustomHttpOptionsProvider = (_url, options) => ({
       ...options,
@@ -132,10 +133,7 @@ export class NodeClient extends AbstractClient {
     const issuerUrl = new URL(issuer.metadata.issuer);
 
     if (config.idpLogout) {
-      if (
-        this.config.idpLogout &&
-        (this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
-      ) {
+      if (config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false)) {
         Object.defineProperty(this.client, 'endSessionUrl', {
           value(params: EndSessionParameters) {
             const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;
diff --git a/src/auth0-session/config.ts b/src/auth0-session/config.ts
index 59f06d23a..191f4aa20 100644
--- a/src/auth0-session/config.ts
+++ b/src/auth0-session/config.ts
@@ -364,3 +364,5 @@ export interface LogoutOptions {
    */
   logoutParams?: { [key: string]: any };
 }
+
+export type GetConfig = Config | (() => Config | Promise<Config>);
diff --git a/src/auth0-session/handlers/callback.ts b/src/auth0-session/handlers/callback.ts
index 6a094593c..47b8d889d 100644
--- a/src/auth0-session/handlers/callback.ts
+++ b/src/auth0-session/handlers/callback.ts
@@ -1,5 +1,5 @@
 import urlJoin from 'url-join';
-import { AuthorizationParameters, Config } from '../config';
+import { AuthorizationParameters, GetConfig, Config } from '../config';
 import TransientStore from '../transient-store';
 import { decodeState } from '../utils/encoding';
 import { SessionCache } from '../session-cache';
@@ -25,12 +25,14 @@ export type CallbackOptions = {
 export type HandleCallback = (req: Auth0Request, res: Auth0Response, options?: CallbackOptions) => Promise<void>;
 
 export default function callbackHandlerFactory(
-  config: Config,
+  getConfig: GetConfig,
   client: AbstractClient,
   sessionCache: SessionCache,
   transientCookieHandler: TransientStore
 ): HandleCallback {
+  const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
   return async (req, res, options) => {
+    const config = await getConfigFn();
     const redirectUri = options?.redirectUri || getRedirectUri(config);
 
     let tokenResponse;
diff --git a/src/auth0-session/handlers/login.ts b/src/auth0-session/handlers/login.ts
index bda12462f..7bf3a98d0 100644
--- a/src/auth0-session/handlers/login.ts
+++ b/src/auth0-session/handlers/login.ts
@@ -1,5 +1,5 @@
 import urlJoin from 'url-join';
-import { Config, LoginOptions } from '../config';
+import { Config, GetConfig, LoginOptions } from '../config';
 import TransientStore from '../transient-store';
 import { encodeState } from '../utils/encoding';
 import createDebug from '../utils/debug';
@@ -23,11 +23,13 @@ export type AuthVerification = {
 };
 
 export default function loginHandlerFactory(
-  config: Config,
+  getConfig: GetConfig,
   client: AbstractClient,
   transientHandler: TransientStore
 ): HandleLogin {
+  const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
   return async (req, res, options = {}) => {
+    const config = await getConfigFn();
     const returnTo = options.returnTo || config.baseURL;
 
     const opts = {
diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts
index b511b8317..01cc01217 100644
--- a/src/auth0-session/handlers/logout.ts
+++ b/src/auth0-session/handlers/logout.ts
@@ -1,6 +1,6 @@
 import urlJoin from 'url-join';
 import createDebug from '../utils/debug';
-import { Config, LogoutOptions } from '../config';
+import { GetConfig, LogoutOptions } from '../config';
 import { SessionCache } from '../session-cache';
 import { Auth0Request, Auth0Response } from '../http';
 import { AbstractClient } from '../client/abstract-client';
@@ -10,11 +10,13 @@ const debug = createDebug('logout');
 export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: LogoutOptions) => Promise<void>;
 
 export default function logoutHandlerFactory(
-  config: Config,
+  getConfig: GetConfig,
   client: AbstractClient,
   sessionCache: SessionCache
 ): HandleLogout {
+  const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
   return async (req, res, options = {}) => {
+    const config = await getConfigFn();
     let returnURL = options.returnTo || config.routes.postLogoutRedirect;
     debug('logout() with return url: %s', returnURL);
 
diff --git a/src/auth0-session/session/abstract-session.ts b/src/auth0-session/session/abstract-session.ts
index 76f158ace..17bd9a8cc 100644
--- a/src/auth0-session/session/abstract-session.ts
+++ b/src/auth0-session/session/abstract-session.ts
@@ -1,6 +1,6 @@
 import createDebug from '../utils/debug';
 import { CookieSerializeOptions } from 'cookie';
-import { Config } from '../config';
+import { Config, GetConfig } from '../config';
 import { Auth0RequestCookies, Auth0ResponseCookies } from '../http';
 
 const debug = createDebug('session');
@@ -36,7 +36,11 @@ const assert = (bool: boolean, msg: string) => {
 };
 
 export abstract class AbstractSession<Session> {
-  constructor(protected config: Config) {}
+  protected getConfig: () => Config | Promise<Config>;
+
+  constructor(getConfig: GetConfig) {
+    this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
+  }
 
   abstract getSession(req: Auth0RequestCookies): Promise<SessionPayload<Session> | undefined | null>;
 
@@ -58,7 +62,8 @@ export abstract class AbstractSession<Session> {
   ): Promise<void>;
 
   public async read(req: Auth0RequestCookies): Promise<[Session?, number?]> {
-    const { rollingDuration, absoluteDuration } = this.config.session;
+    const config = await this.getConfig();
+    const { rollingDuration, absoluteDuration } = config.session;
 
     try {
       const existingSessionValue = await this.getSession(req);
@@ -95,9 +100,10 @@ export abstract class AbstractSession<Session> {
     session: Session | null | undefined,
     createdAt?: number
   ): Promise<void> {
+    const config = await this.getConfig();
     const {
       cookie: { transient, ...cookieConfig }
-    } = this.config.session;
+    } = config.session;
 
     if (!session) {
       await this.deleteSession(req, res, cookieConfig);
@@ -107,7 +113,7 @@ export abstract class AbstractSession<Session> {
     const isNewSession = typeof createdAt === 'undefined';
     const uat = epoch();
     const iat = typeof createdAt === 'number' ? createdAt : uat;
-    const exp = this.calculateExp(iat, uat);
+    const exp = this.calculateExp(iat, uat, config);
 
     const cookieOptions: CookieSerializeOptions = {
       ...cookieConfig
@@ -119,9 +125,9 @@ export abstract class AbstractSession<Session> {
     await this.setSession(req, res, session, uat, iat, exp, cookieOptions, isNewSession);
   }
 
-  private calculateExp(iat: number, uat: number): number {
-    const { absoluteDuration } = this.config.session;
-    const { rolling, rollingDuration } = this.config.session;
+  private calculateExp(iat: number, uat: number, config: Config): number {
+    const { absoluteDuration } = config.session;
+    const { rolling, rollingDuration } = config.session;
 
     if (typeof absoluteDuration !== 'number') {
       return uat + (rollingDuration as number);
diff --git a/src/auth0-session/session/stateful-session.ts b/src/auth0-session/session/stateful-session.ts
index c83cf84d3..e3b64561a 100644
--- a/src/auth0-session/session/stateful-session.ts
+++ b/src/auth0-session/session/stateful-session.ts
@@ -1,6 +1,5 @@
 import { CookieSerializeOptions } from 'cookie';
 import createDebug from '../utils/debug';
-import { Config } from '../config';
 import { AbstractSession, SessionPayload } from './abstract-session';
 import { generateCookieValue, getCookieValue } from '../utils/signed-cookies';
 import { signing } from '../utils/hkdf';
@@ -29,16 +28,20 @@ export class StatefulSession<
   Session extends { [key: string]: any } = { [key: string]: any }
 > extends AbstractSession<Session> {
   private keys?: Uint8Array[];
-  private store: SessionStore<Session>;
+  private store?: SessionStore<Session>;
 
-  constructor(protected config: Config) {
-    super(config);
-    this.store = config.session.store as SessionStore<Session>;
+  private async getStore(): Promise<SessionStore<Session>> {
+    if (!this.store) {
+      const config = await this.getConfig();
+      this.store = config.session.store as SessionStore<Session>;
+    }
+    return this.store;
   }
 
   private async getKeys(): Promise<Uint8Array[]> {
     if (!this.keys) {
-      const secret = this.config.secret;
+      const config = await this.getConfig();
+      const secret = config.secret;
       const secrets = Array.isArray(secret) ? secret : [secret];
       this.keys = await Promise.all(secrets.map(signing));
     }
@@ -46,14 +49,16 @@ export class StatefulSession<
   }
 
   async getSession(req: Auth0RequestCookies): Promise<SessionPayload<Session> | undefined | null> {
-    const { name: sessionName } = this.config.session;
+    const config = await this.getConfig();
+    const { name: sessionName } = config.session;
     const cookies = req.getCookies();
     const keys = await this.getKeys();
     const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);
 
     if (sessionId) {
+      const store = await this.getStore();
       debug('reading session from %s store', sessionId);
-      return this.store.get(sessionId);
+      return store.get(sessionId);
     }
     return;
   }
@@ -68,7 +73,9 @@ export class StatefulSession<
     cookieOptions: CookieSerializeOptions,
     isNewSession: boolean
   ): Promise<void> {
-    const { name: sessionName, genId } = this.config.session;
+    const config = await this.getConfig();
+    const store = await this.getStore();
+    const { name: sessionName, genId } = config.session;
     const cookies = req.getCookies();
     const keys = await this.getKeys();
     let sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);
@@ -77,7 +84,7 @@ export class StatefulSession<
     // from the store and regenerate the session id to prevent session fixation issue.
     if (sessionId && isNewSession) {
       debug('regenerating session id %o to prevent session fixation', sessionId);
-      await this.store.delete(sessionId);
+      await store.delete(sessionId);
       sessionId = undefined;
     }
 
@@ -88,7 +95,7 @@ export class StatefulSession<
     debug('set session %o', sessionId);
     const cookieValue = await generateCookieValue(sessionName, sessionId, keys[0]);
     res.setCookie(sessionName, cookieValue, cookieOptions);
-    await this.store.set(sessionId, {
+    await store.set(sessionId, {
       header: { iat, uat, exp },
       data: session
     });
@@ -99,15 +106,17 @@ export class StatefulSession<
     res: Auth0ResponseCookies,
     cookieOptions: CookieSerializeOptions
   ): Promise<void> {
-    const { name: sessionName } = this.config.session;
+    const config = await this.getConfig();
+    const { name: sessionName } = config.session;
     const cookies = req.getCookies();
     const keys = await this.getKeys();
     const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);
 
     if (sessionId) {
+      const store = await this.getStore();
       debug('deleting session %o', sessionId);
       res.clearCookie(sessionName, cookieOptions);
-      await this.store.delete(sessionId);
+      await store.delete(sessionId);
     }
   }
 }
diff --git a/src/auth0-session/session/stateless-session.ts b/src/auth0-session/session/stateless-session.ts
index 34ee07bcc..31f2f2d1e 100644
--- a/src/auth0-session/session/stateless-session.ts
+++ b/src/auth0-session/session/stateless-session.ts
@@ -18,28 +18,36 @@ export class StatelessSession<
   Session extends { [key: string]: any } = { [key: string]: any }
 > extends AbstractSession<Session> {
   private keys?: Uint8Array[];
-  private chunkSize: number;
+  private chunkSize?: number;
 
   constructor(protected config: Config) {
     super(config);
-    const {
-      cookie: { transient, ...cookieConfig },
-      name: sessionName
-    } = this.config.session;
-    const cookieOptions: CookieSerializeOptions = {
-      ...cookieConfig
-    };
-    if (!transient) {
-      cookieOptions.expires = new Date();
-    }
+  }
 
-    const emptyCookie = serialize(`${sessionName}.0`, '', cookieOptions);
-    this.chunkSize = MAX_COOKIE_SIZE - emptyCookie.length;
+  private async getChunkSize(): Promise<number> {
+    if (this.chunkSize === undefined) {
+      const config = await this.getConfig();
+      const {
+        cookie: { transient, ...cookieConfig },
+        name: sessionName
+      } = config.session;
+      const cookieOptions: CookieSerializeOptions = {
+        ...cookieConfig
+      };
+      if (!transient) {
+        cookieOptions.expires = new Date();
+      }
+
+      const emptyCookie = serialize(`${sessionName}.0`, '', cookieOptions);
+      this.chunkSize = MAX_COOKIE_SIZE - emptyCookie.length;
+    }
+    return this.chunkSize;
   }
 
   private async getKeys(): Promise<Uint8Array[]> {
     if (!this.keys) {
-      const secret = this.config.secret;
+      const config = await this.getConfig();
+      const secret = config.secret;
       const secrets = Array.isArray(secret) ? secret : [secret];
       this.keys = await Promise.all(secrets.map(encryption));
     }
@@ -65,7 +73,8 @@ export class StatelessSession<
   }
 
   async getSession(req: Auth0RequestCookies): Promise<SessionPayload<Session> | undefined | null> {
-    const { name: sessionName } = this.config.session;
+    const config = await this.getConfig();
+    const { name: sessionName } = config.session;
     const cookies = req.getCookies();
     let existingSessionValue: string | undefined;
     if (sessionName in cookies) {
@@ -112,13 +121,15 @@ export class StatelessSession<
     exp: number,
     cookieOptions: CookieSerializeOptions
   ): Promise<void> {
-    const { name: sessionName } = this.config.session;
+    const config = await this.getConfig();
+    const { name: sessionName } = config.session;
     const cookies = req.getCookies();
 
     debug('found session, creating signed session cookie(s) with name %o(.i)', sessionName);
     const value = await this.encrypt(session, { iat, uat, exp });
 
-    const chunkCount = Math.ceil(value.length / this.chunkSize);
+    const chunkSize = await this.getChunkSize();
+    const chunkCount = Math.ceil(value.length / chunkSize);
 
     const existingCookies = new Set(
       Object.keys(cookies).filter((cookie) => cookie.match(`^${sessionName}(?:\\.\\d)?$`))
@@ -127,7 +138,7 @@ export class StatelessSession<
     if (chunkCount > 1) {
       debug('cookie size greater than %d, chunking', this.chunkSize);
       for (let i = 0; i < chunkCount; i++) {
-        const chunkValue = value.slice(i * this.chunkSize, (i + 1) * this.chunkSize);
+        const chunkValue = value.slice(i * chunkSize, (i + 1) * chunkSize);
         const chunkCookieName = `${sessionName}.${i}`;
         res.setCookie(chunkCookieName, chunkValue, cookieOptions);
         existingCookies.delete(chunkCookieName);
@@ -147,7 +158,8 @@ export class StatelessSession<
     res: Auth0ResponseCookies,
     cookieOptions: CookieSerializeOptions
   ): Promise<void> {
-    const { name: sessionName } = this.config.session;
+    const config = await this.getConfig();
+    const { name: sessionName } = config.session;
     const cookies = req.getCookies();
 
     for (const cookieName of Object.keys(cookies)) {
diff --git a/src/auth0-session/transient-store.ts b/src/auth0-session/transient-store.ts
index ee89457b2..ca7fcbdd0 100644
--- a/src/auth0-session/transient-store.ts
+++ b/src/auth0-session/transient-store.ts
@@ -1,6 +1,6 @@
 import { generateCookieValue, getCookieValue } from './utils/signed-cookies';
 import { signing } from './utils/hkdf';
-import { Config } from './config';
+import { Config, GetConfig } from './config';
 import { Auth0Request, Auth0Response } from './http';
 
 export interface StoreOptions {
@@ -11,11 +11,16 @@ export interface StoreOptions {
 export default class TransientStore {
   private keys?: Uint8Array[];
 
-  constructor(private config: Config) {}
+  protected getConfig: () => Config | Promise<Config>;
+
+  constructor(getConfig: GetConfig) {
+    this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
+  }
 
   private async getKeys(): Promise<Uint8Array[]> {
     if (!this.keys) {
-      const secret = this.config.secret;
+      const config = await this.getConfig();
+      const secret = config.secret;
       const secrets = Array.isArray(secret) ? secret : [secret];
       this.keys = await Promise.all(secrets.map(signing));
     }
@@ -41,7 +46,8 @@ export default class TransientStore {
     { sameSite = 'none', value }: StoreOptions
   ): Promise<string> {
     const isSameSiteNone = sameSite === 'none';
-    const { domain, path, secure } = this.config.transactionCookie;
+    const config = await this.getConfig();
+    const { domain, path, secure } = config.transactionCookie;
     const basicAttr = {
       httpOnly: true,
       secure,
@@ -60,7 +66,7 @@ export default class TransientStore {
       });
     }
 
-    if (isSameSiteNone && this.config.legacySameSiteCookie) {
+    if (isSameSiteNone && config.legacySameSiteCookie) {
       const cookieValue = await generateCookieValue(`_${key}`, value, signingKey);
       // Set the fallback cookie with no SameSite or Secure attributes.
       res.setCookie(`_${key}`, cookieValue, basicAttr);
@@ -81,13 +87,14 @@ export default class TransientStore {
   async read(key: string, req: Auth0Request, res: Auth0Response): Promise<string | undefined> {
     const cookies = req.getCookies();
     const cookie = cookies[key];
-    const cookieConfig = this.config.transactionCookie;
+    const config = await this.getConfig();
+    const cookieConfig = config.transactionCookie;
 
     const verifyingKeys = await this.getKeys();
     let value = await getCookieValue(key, cookie, verifyingKeys);
     res.clearCookie(key, cookieConfig);
 
-    if (this.config.legacySameSiteCookie) {
+    if (config.legacySameSiteCookie) {
       const fallbackKey = `_${key}`;
       if (!value) {
         const fallbackCookie = cookies[fallbackKey];
diff --git a/tests/auth0-session/client/edge-client.test.ts b/tests/auth0-session/client/edge-client.test.ts
index 48b7805c9..a4c9afb48 100644
--- a/tests/auth0-session/client/edge-client.test.ts
+++ b/tests/auth0-session/client/edge-client.test.ts
@@ -293,7 +293,8 @@ describe('edge client', function () {
   });
 
   it('should only support code flow', async () => {
-    await expect(getClient({ authorizationParams: { response_type: 'id_token' } })).rejects.toThrow(
+    const client = await getClient({ authorizationParams: { response_type: 'id_token' } });
+    await expect(client.authorizationUrl({})).rejects.toThrow(
       'This SDK only supports `response_type=code` when used in an Edge runtime.'
     );
   });
diff --git a/tests/auth0-session/client/node-client.test.ts b/tests/auth0-session/client/node-client.test.ts
index 1aa983d51..c68fae9d2 100644
--- a/tests/auth0-session/client/node-client.test.ts
+++ b/tests/auth0-session/client/node-client.test.ts
@@ -69,6 +69,21 @@ describe('node client', function () {
     expect(headerProps).not.toContain('auth0-client');
   });
 
+  it('should accept lazy config', async function () {
+    expect(
+      () =>
+        new NodeClient(
+          () => {
+            throw new Error();
+          },
+          {
+            name: 'nextjs-auth0',
+            version
+          }
+        )
+    ).not.toThrow();
+  });
+
   it('should not strip new headers', async function () {
     const client = await getClient();
     const response = await client.userinfo('__test_token__');
diff --git a/tests/auth0-session/handlers/callback.test.ts b/tests/auth0-session/handlers/callback.test.ts
index 332eaeebf..5623c79fd 100644
--- a/tests/auth0-session/handlers/callback.test.ts
+++ b/tests/auth0-session/handlers/callback.test.ts
@@ -10,6 +10,7 @@ import { IncomingMessage, ServerResponse } from 'http';
 import { readFileSync } from 'fs';
 import { join } from 'path';
 import * as qs from 'querystring';
+import callbackHandlerFactory from '../../../src/auth0-session/handlers/callback';
 
 const privateKey = readFileSync(join(__dirname, '..', 'fixtures', 'private-key.pem'), 'utf-8');
 
@@ -20,6 +21,13 @@ const authVerificationCookie = (cookies: Record<string, string>) => ({ auth_veri
 describe('callback', () => {
   afterEach(teardown);
 
+  it('should accept lazy config', () => {
+    const getConfig = () => {
+      throw new Error();
+    };
+    expect(() => (callbackHandlerFactory as any)(getConfig)).not.toThrow();
+  });
+
   it('should error when the body is empty', async () => {
     const baseURL = await setup(defaultConfig);
 
diff --git a/tests/auth0-session/handlers/login.test.ts b/tests/auth0-session/handlers/login.test.ts
index 6b01a47d8..a9f16ff87 100644
--- a/tests/auth0-session/handlers/login.test.ts
+++ b/tests/auth0-session/handlers/login.test.ts
@@ -4,12 +4,20 @@ import { setup, teardown } from '../fixtures/server';
 import { defaultConfig, fromCookieJar, get, getCookie } from '../fixtures/helpers';
 import { decodeState, encodeState } from '../../../src/auth0-session/utils/encoding';
 import { LoginOptions } from '../../../src/auth0-session';
+import loginHandlerFactory from '../../../src/auth0-session/handlers/login';
 
 const authVerificationCookie = (cookie: string) => JSON.parse(decodeURIComponent(cookie));
 
 describe('login', () => {
   afterEach(teardown);
 
+  it('should accept lazy config', () => {
+    const getConfig = () => {
+      throw new Error();
+    };
+    expect(() => (loginHandlerFactory as any)(getConfig)).not.toThrow();
+  });
+
   it('should redirect to the authorize url for /login', async () => {
     const baseURL = await setup(defaultConfig);
     const cookieJar = new CookieJar();
diff --git a/tests/auth0-session/handlers/logout.test.ts b/tests/auth0-session/handlers/logout.test.ts
index 2afb75209..83517b9ad 100644
--- a/tests/auth0-session/handlers/logout.test.ts
+++ b/tests/auth0-session/handlers/logout.test.ts
@@ -6,6 +6,7 @@ import { toSignedCookieJar, defaultConfig, get, post, fromCookieJar } from '../f
 import { makeIdToken } from '../fixtures/cert';
 import { encodeState } from '../../../src/auth0-session/utils/encoding';
 import wellKnown from '../fixtures/well-known.json';
+import logoutHandlerFactory from '../../../src/auth0-session/handlers/logout';
 
 const login = async (baseURL: string): Promise<CookieJar> => {
   const nonce = '__test_nonce__';
@@ -24,6 +25,13 @@ const login = async (baseURL: string): Promise<CookieJar> => {
 describe('logout route', () => {
   afterEach(teardown);
 
+  it('should accept lazy config', () => {
+    const getConfig = () => {
+      throw new Error();
+    };
+    expect(() => (logoutHandlerFactory as any)(getConfig)).not.toThrow();
+  });
+
   it('should perform a local logout', async () => {
     const baseURL = await setup({ ...defaultConfig, idpLogout: false });
     const cookieJar = await login(baseURL);
diff --git a/tests/auth0-session/session/stateless-session.test.ts b/tests/auth0-session/session/stateless-session.test.ts
index 7eb01c336..b00d5d95f 100644
--- a/tests/auth0-session/session/stateless-session.test.ts
+++ b/tests/auth0-session/session/stateless-session.test.ts
@@ -9,6 +9,7 @@ import { setup, teardown } from '../fixtures/server';
 import { defaultConfig, fromCookieJar, get, toCookieJar } from '../fixtures/helpers';
 import { encryption } from '../../../src/auth0-session/utils/hkdf';
 import { makeIdToken } from '../fixtures/cert';
+import { StatelessSession } from '../../../src/auth0-session';
 
 const hr = 60 * 60 * 1000;
 const day = 24 * hr;
@@ -38,6 +39,15 @@ const encrypted = async (claims: Partial<IdTokenClaims> = { sub: '__test_sub__'
 describe('StatelessSession', () => {
   afterEach(teardown);
 
+  it('should accept lazy config', () => {
+    expect(
+      () =>
+        new StatelessSession((() => {
+          throw new Error();
+        }) as any)
+    ).not.toThrow();
+  });
+
   it('should not create a session when there are no cookies', async () => {
     const baseURL = await setup(defaultConfig);
     await expect(get(baseURL, '/session')).rejects.toThrowError('Unauthorized');
diff --git a/tests/auth0-session/transient-store.test.ts b/tests/auth0-session/transient-store.test.ts
index 426371aeb..898ae0346 100644
--- a/tests/auth0-session/transient-store.test.ts
+++ b/tests/auth0-session/transient-store.test.ts
@@ -30,6 +30,15 @@ const setup = async (
 describe('TransientStore', () => {
   afterEach(teardown);
 
+  it('should accept lazy config', () => {
+    expect(
+      () =>
+        new TransientStore(() => {
+          throw new Error();
+        })
+    ).not.toThrow();
+  });
+
   it('should use the passed-in key to set the cookies', async () => {
     const baseURL: string = await setup(defaultConfig, async (req: NodeRequest, res: NodeResponse) =>
       transientStore.save('test_key', req, res, { value: 'foo' })

From d79ff8f59accdf0535d616ae80dc57de343317e9 Mon Sep 17 00:00:00 2001
From: Adam Mcgrath <adamjmcgrath@gmail.com>
Date: Thu, 2 Nov 2023 14:18:05 +0000
Subject: [PATCH 2/7] Allow lazy config in Next.js layer

---
 package.json                                  |   1 +
 src/auth0-session/client/abstract-client.ts   |   8 +-
 src/auth0-session/client/edge-client.ts       | 172 +++----
 src/auth0-session/client/node-client.ts       | 271 ++++++-----
 src/auth0-session/config.ts                   |   3 +-
 src/auth0-session/handlers/callback.ts        |   9 +-
 src/auth0-session/handlers/login.ts           |   7 +-
 src/auth0-session/handlers/logout.ts          |   7 +-
 src/auth0-session/index.ts                    |  12 +-
 src/auth0-session/session-cache.ts            |   2 +-
 src/auth0-session/session/abstract-session.ts |   6 +-
 src/auth0-session/session/stateful-session.ts |  25 +-
 .../session/stateless-session.ts              |  26 +-
 src/auth0-session/transient-store.ts          |  19 +-
 src/config.ts                                 | 428 ++----------------
 src/edge.ts                                   |  25 +-
 src/handlers/callback.ts                      |  33 +-
 src/handlers/login.ts                         |  45 +-
 src/handlers/logout.ts                        |   1 -
 src/handlers/profile.ts                       |  25 +-
 src/helpers/testing.ts                        |   7 +-
 src/helpers/with-middleware-auth-required.ts  |  11 +-
 src/helpers/with-page-auth-required.ts        |  23 +-
 src/index.ts                                  |  28 +-
 src/init.ts                                   |  55 +--
 src/session/cache.ts                          |  52 ++-
 src/session/get-access-token.ts               |  16 +-
 src/session/session.ts                        |   6 +-
 .../auth0-session/client/edge-client.test.ts  |  29 +-
 .../auth0-session/client/node-client.test.ts  |  38 +-
 tests/auth0-session/fixtures/server.ts        |  16 +-
 tests/config.test.ts                          |  62 +--
 tests/fixtures/app-router-helpers.ts          |   2 +-
 tests/helpers/testing.test.ts                 |  17 +-
 tests/index.test.ts                           |  19 +-
 tests/session/cache.test.ts                   |   6 +-
 tests/session/session.test.ts                 |  63 ++-
 37 files changed, 612 insertions(+), 963 deletions(-)

diff --git a/package.json b/package.json
index ec0c11510..de45cebaf 100644
--- a/package.json
+++ b/package.json
@@ -139,6 +139,7 @@
       "!<rootDir>/src/edge.ts",
       "!<rootDir>/src/index.ts",
       "!<rootDir>/src/shared.ts",
+      "!<rootDir>/src/version.ts",
       "!<rootDir>/src/auth0-session/config.ts",
       "!<rootDir>/src/auth0-session/index.ts",
       "!<rootDir>/src/auth0-session/session-cache.ts"
diff --git a/src/auth0-session/client/abstract-client.ts b/src/auth0-session/client/abstract-client.ts
index 25f0c9360..48f92edfd 100644
--- a/src/auth0-session/client/abstract-client.ts
+++ b/src/auth0-session/client/abstract-client.ts
@@ -1,5 +1,5 @@
-import { Config, GetConfig } from '../config';
 import { Auth0Request } from '../http';
+import { Config } from '../config';
 
 export type Telemetry = {
   name: string;
@@ -85,10 +85,6 @@ export interface AuthorizationParameters {
 }
 
 export abstract class AbstractClient {
-  protected getConfig: () => Config | Promise<Config>;
-  constructor(getConfig: GetConfig, protected telemetry: Telemetry) {
-    this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
-  }
   abstract authorizationUrl(parameters: Record<string, unknown>): Promise<string>;
   abstract callbackParams(req: Auth0Request, expectedState: string): Promise<URLSearchParams>;
   abstract callback(
@@ -107,3 +103,5 @@ export abstract class AbstractClient {
   abstract generateRandomNonce(): string;
   abstract calculateCodeChallenge(codeVerifier: string): Promise<string> | string;
 }
+
+export type GetClient = (config: Config) => Promise<AbstractClient>;
diff --git a/src/auth0-session/client/edge-client.ts b/src/auth0-session/client/edge-client.ts
index 1480f7bca..4d80ccb48 100644
--- a/src/auth0-session/client/edge-client.ts
+++ b/src/auth0-session/client/edge-client.ts
@@ -6,11 +6,13 @@ import {
   OpenIDCallbackChecks,
   TokenEndpointResponse,
   AbstractClient,
-  EndSessionParameters
+  EndSessionParameters,
+  Telemetry
 } from './abstract-client';
 import { ApplicationError, DiscoveryError, IdentityProviderError, UserInfoError } from '../utils/errors';
 import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors';
 import urlJoin from 'url-join';
+import { Config } from '../config';
 
 const encodeBase64 = (input: string) => {
   const unencoded = new TextEncoder().encode(input);
@@ -24,68 +26,18 @@ const encodeBase64 = (input: string) => {
 };
 
 export class EdgeClient extends AbstractClient {
-  private client?: oauth.Client;
-  private as?: oauth.AuthorizationServer;
-
-  private async httpOptions(): Promise<oauth.HttpRequestOptions> {
-    const headers = new Headers();
-    const config = await this.getConfig();
-    if (config.enableTelemetry) {
-      const { name, version } = this.telemetry;
-      headers.set('User-Agent', `${name}/${version}`);
-      headers.set(
-        'Auth0-Client',
-        encodeBase64(
-          JSON.stringify({
-            name,
-            version,
-            env: {
-              edge: true
-            }
-          })
-        )
-      );
-    }
-    return {
-      signal: AbortSignal.timeout(config.httpTimeout),
-      headers
-    };
-  }
-
-  private async getClient(): Promise<[oauth.AuthorizationServer, oauth.Client]> {
-    if (this.as) {
-      return [this.as, this.client as oauth.Client];
-    }
-    const config = await this.getConfig();
-    if (config.authorizationParams.response_type !== 'code') {
-      throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
-    }
-
-    const issuer = new URL(config.issuerBaseURL);
-    try {
-      this.as = await oauth
-        .discoveryRequest(issuer, await this.httpOptions())
-        .then((response) => oauth.processDiscoveryResponse(issuer, response));
-    } catch (e) {
-      throw new DiscoveryError(e, config.issuerBaseURL);
-    }
-
-    this.client = {
-      client_id: config.clientID,
-      ...(!config.clientAssertionSigningKey && { client_secret: config.clientSecret }),
-      token_endpoint_auth_method: config.clientAuthMethod,
-      id_token_signed_response_alg: config.idTokenSigningAlg,
-      [oauth.clockTolerance]: config.clockTolerance
-    };
-
-    return [this.as, this.client];
+  constructor(
+    private client: oauth.Client,
+    private as: oauth.AuthorizationServer,
+    private config: Config,
+    private httpOptions: oauth.HttpRequestOptions
+  ) {
+    super();
   }
 
   async authorizationUrl(parameters: Record<string, unknown>): Promise<string> {
-    const [as] = await this.getClient();
-    const config = await this.getConfig();
-    const authorizationUrl = new URL(as.authorization_endpoint as string);
-    authorizationUrl.searchParams.set('client_id', config.clientID);
+    const authorizationUrl = new URL(this.as.authorization_endpoint as string);
+    authorizationUrl.searchParams.set('client_id', this.config.clientID);
     Object.entries(parameters).forEach(([key, value]) => {
       if (value === null || value === undefined) {
         return;
@@ -96,12 +48,11 @@ export class EdgeClient extends AbstractClient {
   }
 
   async callbackParams(req: Auth0Request, expectedState: string) {
-    const [as, client] = await this.getClient();
     const url =
       req.getMethod().toUpperCase() === 'GET' ? new URL(req.getUrl()) : new URLSearchParams(await req.getBody());
     let result: ReturnType<typeof oauth.validateAuthResponse>;
     try {
-      result = oauth.validateAuthResponse(as, client, url, expectedState);
+      result = oauth.validateAuthResponse(this.as, this.client, url, expectedState);
     } catch (e) {
       throw new ApplicationError(e);
     }
@@ -121,8 +72,7 @@ export class EdgeClient extends AbstractClient {
     checks: OpenIDCallbackChecks,
     extras: CallbackExtras
   ): Promise<TokenEndpointResponse> {
-    const [as, client] = await this.getClient();
-    const { clientAssertionSigningKey, clientAssertionSigningAlg } = await this.getConfig();
+    const { clientAssertionSigningKey, clientAssertionSigningAlg } = this.config;
 
     let clientPrivateKey = clientAssertionSigningKey as CryptoKey | undefined;
     /* c8 ignore next 3 */
@@ -130,21 +80,21 @@ export class EdgeClient extends AbstractClient {
       clientPrivateKey = await jose.importPKCS8<CryptoKey>(clientPrivateKey, clientAssertionSigningAlg || 'RS256');
     }
     const response = await oauth.authorizationCodeGrantRequest(
-      as,
-      client,
+      this.as,
+      this.client,
       parameters,
       redirectUri,
       checks.code_verifier as string,
       {
         additionalParameters: extras.exchangeBody,
         ...(clientPrivateKey && { clientPrivateKey }),
-        ...this.httpOptions()
+        ...this.httpOptions
       }
     );
 
     const result = await oauth.processAuthorizationCodeOpenIDResponse(
-      as,
-      client,
+      this.as,
+      this.client,
       response,
       checks.nonce,
       checks.max_age
@@ -160,18 +110,16 @@ export class EdgeClient extends AbstractClient {
   }
 
   async endSessionUrl(parameters: EndSessionParameters): Promise<string> {
-    const [as] = await this.getClient();
-    const issuerUrl = new URL(as.issuer);
-    const config = await this.getConfig();
+    const issuerUrl = new URL(this.as.issuer);
 
     if (
-      config.idpLogout &&
-      (config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false))
+      this.config.idpLogout &&
+      (this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
     ) {
       const { id_token_hint, post_logout_redirect_uri, ...extraParams } = parameters;
-      const auth0LogoutUrl: URL = new URL(urlJoin(as.issuer, '/v2/logout'));
+      const auth0LogoutUrl: URL = new URL(urlJoin(this.as.issuer, '/v2/logout'));
       post_logout_redirect_uri && auth0LogoutUrl.searchParams.set('returnTo', post_logout_redirect_uri);
-      auth0LogoutUrl.searchParams.set('client_id', config.clientID);
+      auth0LogoutUrl.searchParams.set('client_id', this.config.clientID);
       Object.entries(extraParams).forEach(([key, value]: [string, string]) => {
         if (value === null || value === undefined) {
           return;
@@ -180,10 +128,10 @@ export class EdgeClient extends AbstractClient {
       });
       return auth0LogoutUrl.toString();
     }
-    if (!as.end_session_endpoint) {
+    if (!this.as.end_session_endpoint) {
       throw new Error('RP Initiated Logout is not supported on your Authorization Server.');
     }
-    const oidcLogoutUrl = new URL(as.end_session_endpoint);
+    const oidcLogoutUrl = new URL(this.as.end_session_endpoint);
     Object.entries(parameters).forEach(([key, value]: [string, string]) => {
       if (value === null || value === undefined) {
         return;
@@ -191,28 +139,26 @@ export class EdgeClient extends AbstractClient {
       oidcLogoutUrl.searchParams.set(key, value);
     });
 
-    oidcLogoutUrl.searchParams.set('client_id', config.clientID);
+    oidcLogoutUrl.searchParams.set('client_id', this.config.clientID);
     return oidcLogoutUrl.toString();
   }
 
   async userinfo(accessToken: string): Promise<Record<string, unknown>> {
-    const [as, client] = await this.getClient();
-    const response = await oauth.userInfoRequest(as, client, accessToken, await this.httpOptions());
+    const response = await oauth.userInfoRequest(this.as, this.client, accessToken, this.httpOptions);
 
     try {
-      return await oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response);
+      return await oauth.processUserInfoResponse(this.as, this.client, oauth.skipSubjectCheck, response);
     } catch (e) {
       throw new UserInfoError(e.message);
     }
   }
 
   async refresh(refreshToken: string, extras: { exchangeBody: Record<string, any> }): Promise<TokenEndpointResponse> {
-    const [as, client] = await this.getClient();
-    const res = await oauth.refreshTokenGrantRequest(as, client, refreshToken, {
+    const res = await oauth.refreshTokenGrantRequest(this.as, this.client, refreshToken, {
       additionalParameters: extras.exchangeBody,
-      ...this.httpOptions()
+      ...this.httpOptions
     });
-    const result = await oauth.processRefreshTokenResponse(as, client, res);
+    const result = await oauth.processRefreshTokenResponse(this.as, this.client, res);
     if (oauth.isOAuth2Error(result)) {
       throw new AccessTokenError(
         AccessTokenErrorCode.FAILED_REFRESH_GRANT,
@@ -239,3 +185,57 @@ export class EdgeClient extends AbstractClient {
     return oauth.calculatePKCECodeChallenge(codeVerifier);
   }
 }
+
+export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise<EdgeClient>) => {
+  let client: EdgeClient;
+  return async (config) => {
+    if (!client) {
+      const headers = new Headers();
+      if (config.enableTelemetry) {
+        const { name, version } = telemetry;
+        headers.set('User-Agent', `${name}/${version}`);
+        headers.set(
+          'Auth0-Client',
+          encodeBase64(
+            JSON.stringify({
+              name,
+              version,
+              env: {
+                edge: true
+              }
+            })
+          )
+        );
+      }
+      const httpOptions: oauth.HttpRequestOptions = {
+        signal: AbortSignal.timeout(config.httpTimeout),
+        headers
+      };
+
+      if (config.authorizationParams.response_type !== 'code') {
+        throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
+      }
+
+      const issuer = new URL(config.issuerBaseURL);
+      let as: oauth.AuthorizationServer;
+      try {
+        as = await oauth
+          .discoveryRequest(issuer, httpOptions)
+          .then((response) => oauth.processDiscoveryResponse(issuer, response));
+      } catch (e) {
+        throw new DiscoveryError(e, config.issuerBaseURL);
+      }
+
+      const oauthClient: oauth.Client = {
+        client_id: config.clientID,
+        ...(!config.clientAssertionSigningKey && { client_secret: config.clientSecret }),
+        token_endpoint_auth_method: config.clientAuthMethod,
+        id_token_signed_response_alg: config.idTokenSigningAlg,
+        [oauth.clockTolerance]: config.clockTolerance
+      };
+
+      client = new EdgeClient(oauthClient, as, config, httpOptions);
+    }
+    return client;
+  };
+};
diff --git a/src/auth0-session/client/node-client.ts b/src/auth0-session/client/node-client.ts
index 683f1f306..da56a4b52 100644
--- a/src/auth0-session/client/node-client.ts
+++ b/src/auth0-session/client/node-client.ts
@@ -4,7 +4,8 @@ import {
   CallbackParamsType,
   OpenIDCallbackChecks,
   TokenEndpointResponse,
-  AbstractClient
+  AbstractClient,
+  Telemetry
 } from './abstract-client';
 import {
   Client,
@@ -23,6 +24,7 @@ import urlJoin from 'url-join';
 import createDebug from '../utils/debug';
 import { IncomingMessage } from 'http';
 import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors';
+import { Config } from '../config';
 
 const debug = createDebug('client');
 
@@ -31,140 +33,16 @@ function sortSpaceDelimitedString(str: string): string {
 }
 
 export class NodeClient extends AbstractClient {
-  private client?: Client;
-
-  private async getClient(): Promise<Client> {
-    if (this.client) {
-      return this.client;
-    }
-    const {
-      getConfig,
-      telemetry: { name, version }
-    } = this;
-    const config = await getConfig();
-
-    const defaultHttpOptions: CustomHttpOptionsProvider = (_url, options) => ({
-      ...options,
-      headers: {
-        ...options.headers,
-        'User-Agent': `${name}/${version}`,
-        ...(config.enableTelemetry
-          ? {
-              'Auth0-Client': Buffer.from(
-                JSON.stringify({
-                  name,
-                  version,
-                  env: {
-                    node: process.version
-                  }
-                })
-              ).toString('base64')
-            }
-          : undefined)
-      },
-      timeout: config.httpTimeout,
-      agent: config.httpAgent
-    });
-    const applyHttpOptionsCustom = (entity: Issuer<Client> | typeof Issuer | Client) => {
-      entity[custom.http_options] = defaultHttpOptions;
-    };
-
-    applyHttpOptionsCustom(Issuer);
-    let issuer: Issuer<Client>;
-    try {
-      issuer = await Issuer.discover(config.issuerBaseURL);
-    } catch (e) {
-      throw new DiscoveryError(e, config.issuerBaseURL);
-    }
-    applyHttpOptionsCustom(issuer);
-
-    const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported)
-      ? issuer.id_token_signing_alg_values_supported
-      : [];
-    if (!issuerTokenAlgs.includes(config.idTokenSigningAlg)) {
-      debug(
-        'ID token algorithm %o is not supported by the issuer. Supported ID token algorithms are: %o.',
-        config.idTokenSigningAlg,
-        issuerTokenAlgs
-      );
-    }
-
-    const configRespType = sortSpaceDelimitedString(config.authorizationParams.response_type);
-    const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : [];
-    issuerRespTypes.map(sortSpaceDelimitedString);
-    if (!issuerRespTypes.includes(configRespType)) {
-      debug(
-        'Response type %o is not supported by the issuer. Supported response types are: %o.',
-        configRespType,
-        issuerRespTypes
-      );
-    }
-
-    const configRespMode = config.authorizationParams.response_mode;
-    const issuerRespModes = Array.isArray(issuer.response_modes_supported) ? issuer.response_modes_supported : [];
-    if (configRespMode && !issuerRespModes.includes(configRespMode)) {
-      debug(
-        'Response mode %o is not supported by the issuer. Supported response modes are %o.',
-        configRespMode,
-        issuerRespModes
-      );
-    }
-
-    let jwks;
-    if (config.clientAssertionSigningKey) {
-      const privateKey = createPrivateKey({ key: config.clientAssertionSigningKey as string });
-      const jwk = await exportJWK(privateKey);
-      jwks = { keys: [jwk] };
-    }
-
-    this.client = new issuer.Client(
-      {
-        client_id: config.clientID,
-        client_secret: config.clientSecret,
-        id_token_signed_response_alg: config.idTokenSigningAlg,
-        token_endpoint_auth_method: config.clientAuthMethod as ClientAuthMethod,
-        token_endpoint_auth_signing_alg: config.clientAssertionSigningAlg
-      },
-      jwks
-    );
-    applyHttpOptionsCustom(this.client);
-
-    this.client[custom.clock_tolerance] = config.clockTolerance;
-    const issuerUrl = new URL(issuer.metadata.issuer);
-
-    if (config.idpLogout) {
-      if (config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false)) {
-        Object.defineProperty(this.client, 'endSessionUrl', {
-          value(params: EndSessionParameters) {
-            const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;
-            const parsedUrl = new URL(urlJoin(issuer.metadata.issuer, '/v2/logout'));
-            parsedUrl.searchParams.set('client_id', config.clientID);
-            post_logout_redirect_uri && parsedUrl.searchParams.set('returnTo', post_logout_redirect_uri);
-            Object.entries(extraParams).forEach(([key, value]) => {
-              if (value === null || value === undefined) {
-                return;
-              }
-              parsedUrl.searchParams.set(key, value as string);
-            });
-            return parsedUrl.toString();
-          }
-        });
-      } else if (!issuer.end_session_endpoint) {
-        debug('the issuer does not support RP-Initiated Logout');
-      }
-    }
-
-    return this.client;
+  constructor(private client: Client) {
+    super();
   }
 
   async authorizationUrl(parameters: Record<string, unknown>): Promise<string> {
-    const client = await this.getClient();
-    return client.authorizationUrl(parameters);
+    return this.client.authorizationUrl(parameters);
   }
 
   async callbackParams(req: Auth0Request) {
-    const client = await this.getClient();
-    const obj: CallbackParamsType = client.callbackParams({
+    const obj: CallbackParamsType = this.client.callbackParams({
       method: req.getMethod(),
       url: req.getUrl(),
       body: await req.getBody()
@@ -179,9 +57,8 @@ export class NodeClient extends AbstractClient {
     extras: CallbackExtras
   ): Promise<TokenEndpointResponse> {
     const params = Object.fromEntries(parameters.entries());
-    const client = await this.getClient();
     try {
-      return await client.callback(redirectUri, params, checks, extras);
+      return await this.client.callback(redirectUri, params, checks, extras);
     } catch (err) {
       if (err instanceof errors.OPError) {
         throw new IdentityProviderError(err);
@@ -195,23 +72,20 @@ export class NodeClient extends AbstractClient {
   }
 
   async endSessionUrl(parameters: EndSessionParameters): Promise<string> {
-    const client = await this.getClient();
-    return client.endSessionUrl(parameters);
+    return this.client.endSessionUrl(parameters);
   }
 
   async userinfo(accessToken: string): Promise<Record<string, unknown>> {
-    const client = await this.getClient();
     try {
-      return await client.userinfo(accessToken);
+      return await this.client.userinfo(accessToken);
     } catch (e) {
       throw new UserInfoError(e.message);
     }
   }
 
   async refresh(refreshToken: string, extras: { exchangeBody: Record<string, any> }): Promise<TokenEndpointResponse> {
-    const client = await this.getClient();
     try {
-      return await client.refresh(refreshToken, extras);
+      return await this.client.refresh(refreshToken, extras);
     } catch (e) {
       throw new AccessTokenError(
         AccessTokenErrorCode.FAILED_REFRESH_GRANT,
@@ -233,3 +107,126 @@ export class NodeClient extends AbstractClient {
     return generators.codeChallenge(codeVerifier);
   }
 }
+
+export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise<NodeClient>) => {
+  let client: NodeClient;
+  return async (config) => {
+    if (!client) {
+      const { name, version } = telemetry;
+
+      const defaultHttpOptions: CustomHttpOptionsProvider = (_url, options) => ({
+        ...options,
+        headers: {
+          ...options.headers,
+          'User-Agent': `${name}/${version}`,
+          ...(config.enableTelemetry
+            ? {
+                'Auth0-Client': Buffer.from(
+                  JSON.stringify({
+                    name,
+                    version,
+                    env: {
+                      node: process.version
+                    }
+                  })
+                ).toString('base64')
+              }
+            : undefined)
+        },
+        timeout: config.httpTimeout,
+        agent: config.httpAgent
+      });
+      const applyHttpOptionsCustom = (entity: Issuer<Client> | typeof Issuer | Client) => {
+        entity[custom.http_options] = defaultHttpOptions;
+      };
+
+      applyHttpOptionsCustom(Issuer);
+      let issuer: Issuer<Client>;
+      try {
+        issuer = await Issuer.discover(config.issuerBaseURL);
+      } catch (e) {
+        throw new DiscoveryError(e, config.issuerBaseURL);
+      }
+      applyHttpOptionsCustom(issuer);
+
+      const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported)
+        ? issuer.id_token_signing_alg_values_supported
+        : [];
+      if (!issuerTokenAlgs.includes(config.idTokenSigningAlg)) {
+        debug(
+          'ID token algorithm %o is not supported by the issuer. Supported ID token algorithms are: %o.',
+          config.idTokenSigningAlg,
+          issuerTokenAlgs
+        );
+      }
+
+      const configRespType = sortSpaceDelimitedString(config.authorizationParams.response_type);
+      const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : [];
+      issuerRespTypes.map(sortSpaceDelimitedString);
+      if (!issuerRespTypes.includes(configRespType)) {
+        debug(
+          'Response type %o is not supported by the issuer. Supported response types are: %o.',
+          configRespType,
+          issuerRespTypes
+        );
+      }
+
+      const configRespMode = config.authorizationParams.response_mode;
+      const issuerRespModes = Array.isArray(issuer.response_modes_supported) ? issuer.response_modes_supported : [];
+      if (configRespMode && !issuerRespModes.includes(configRespMode)) {
+        debug(
+          'Response mode %o is not supported by the issuer. Supported response modes are %o.',
+          configRespMode,
+          issuerRespModes
+        );
+      }
+
+      let jwks;
+      if (config.clientAssertionSigningKey) {
+        const privateKey = createPrivateKey({ key: config.clientAssertionSigningKey as string });
+        const jwk = await exportJWK(privateKey);
+        jwks = { keys: [jwk] };
+      }
+
+      const oidcClient = new issuer.Client(
+        {
+          client_id: config.clientID,
+          client_secret: config.clientSecret,
+          id_token_signed_response_alg: config.idTokenSigningAlg,
+          token_endpoint_auth_method: config.clientAuthMethod as ClientAuthMethod,
+          token_endpoint_auth_signing_alg: config.clientAssertionSigningAlg
+        },
+        jwks
+      );
+      applyHttpOptionsCustom(oidcClient);
+
+      oidcClient[custom.clock_tolerance] = config.clockTolerance;
+      const issuerUrl = new URL(issuer.metadata.issuer);
+
+      if (config.idpLogout) {
+        if (config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false)) {
+          Object.defineProperty(oidcClient, 'endSessionUrl', {
+            value(params: EndSessionParameters) {
+              const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;
+              const parsedUrl = new URL(urlJoin(issuer.metadata.issuer, '/v2/logout'));
+              parsedUrl.searchParams.set('client_id', config.clientID);
+              post_logout_redirect_uri && parsedUrl.searchParams.set('returnTo', post_logout_redirect_uri);
+              Object.entries(extraParams).forEach(([key, value]) => {
+                if (value === null || value === undefined) {
+                  return;
+                }
+                parsedUrl.searchParams.set(key, value as string);
+              });
+              return parsedUrl.toString();
+            }
+          });
+        } else if (!issuer.end_session_endpoint) {
+          debug('the issuer does not support RP-Initiated Logout');
+        }
+      }
+
+      client = new NodeClient(oidcClient);
+    }
+    return client;
+  };
+};
diff --git a/src/auth0-session/config.ts b/src/auth0-session/config.ts
index 191f4aa20..91c2b2644 100644
--- a/src/auth0-session/config.ts
+++ b/src/auth0-session/config.ts
@@ -4,6 +4,7 @@ import type {
 } from './client/abstract-client';
 import type { Agent } from 'https';
 import { SessionStore } from './session/stateful-session';
+import { Auth0RequestCookies } from './http';
 
 /**
  * Configuration properties.
@@ -365,4 +366,4 @@ export interface LogoutOptions {
   logoutParams?: { [key: string]: any };
 }
 
-export type GetConfig = Config | (() => Config | Promise<Config>);
+export type GetConfig = Config | ((req: Auth0RequestCookies) => Config | Promise<Config>);
diff --git a/src/auth0-session/handlers/callback.ts b/src/auth0-session/handlers/callback.ts
index 47b8d889d..69be17d3d 100644
--- a/src/auth0-session/handlers/callback.ts
+++ b/src/auth0-session/handlers/callback.ts
@@ -5,7 +5,7 @@ import { decodeState } from '../utils/encoding';
 import { SessionCache } from '../session-cache';
 import { MalformedStateCookieError, MissingStateCookieError, MissingStateParamError } from '../utils/errors';
 import { Auth0Request, Auth0Response } from '../http';
-import { AbstractClient } from '../client/abstract-client';
+import { GetClient } from '../client/abstract-client';
 import type { AuthVerification } from './login';
 
 function getRedirectUri(config: Config): string {
@@ -26,13 +26,14 @@ export type HandleCallback = (req: Auth0Request, res: Auth0Response, options?: C
 
 export default function callbackHandlerFactory(
   getConfig: GetConfig,
-  client: AbstractClient,
+  getClient: GetClient,
   sessionCache: SessionCache,
   transientCookieHandler: TransientStore
 ): HandleCallback {
   const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
   return async (req, res, options) => {
-    const config = await getConfigFn();
+    const config = await getConfigFn(req);
+    const client = await getClient(config);
     const redirectUri = options?.redirectUri || getRedirectUri(config);
 
     let tokenResponse;
@@ -93,7 +94,7 @@ export default function callbackHandlerFactory(
     }
 
     const openidState: { returnTo?: string } = decodeState(expectedState as string)!;
-    let session = sessionCache.fromTokenEndpointResponse(tokenResponse);
+    let session = await sessionCache.fromTokenEndpointResponse(req, res, tokenResponse);
 
     if (options?.afterCallback) {
       session = await options.afterCallback(session, openidState);
diff --git a/src/auth0-session/handlers/login.ts b/src/auth0-session/handlers/login.ts
index 7bf3a98d0..06f24ba2d 100644
--- a/src/auth0-session/handlers/login.ts
+++ b/src/auth0-session/handlers/login.ts
@@ -4,7 +4,7 @@ import TransientStore from '../transient-store';
 import { encodeState } from '../utils/encoding';
 import createDebug from '../utils/debug';
 import { Auth0Request, Auth0Response } from '../http';
-import { AbstractClient } from '../client/abstract-client';
+import { GetClient } from '../client/abstract-client';
 
 const debug = createDebug('handlers');
 
@@ -24,12 +24,13 @@ export type AuthVerification = {
 
 export default function loginHandlerFactory(
   getConfig: GetConfig,
-  client: AbstractClient,
+  getClient: GetClient,
   transientHandler: TransientStore
 ): HandleLogin {
   const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
   return async (req, res, options = {}) => {
-    const config = await getConfigFn();
+    const config = await getConfigFn(req);
+    const client = await getClient(config);
     const returnTo = options.returnTo || config.baseURL;
 
     const opts = {
diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts
index 01cc01217..984301378 100644
--- a/src/auth0-session/handlers/logout.ts
+++ b/src/auth0-session/handlers/logout.ts
@@ -3,7 +3,7 @@ import createDebug from '../utils/debug';
 import { GetConfig, LogoutOptions } from '../config';
 import { SessionCache } from '../session-cache';
 import { Auth0Request, Auth0Response } from '../http';
-import { AbstractClient } from '../client/abstract-client';
+import { GetClient } from '../client/abstract-client';
 
 const debug = createDebug('logout');
 
@@ -11,12 +11,13 @@ export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: Log
 
 export default function logoutHandlerFactory(
   getConfig: GetConfig,
-  client: AbstractClient,
+  getClient: GetClient,
   sessionCache: SessionCache
 ): HandleLogout {
   const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
   return async (req, res, options = {}) => {
-    const config = await getConfigFn();
+    const config = await getConfigFn(req);
+    const client = await getClient(config);
     let returnURL = options.returnTo || config.routes.postLogoutRedirect;
     debug('logout() with return url: %s', returnURL);
 
diff --git a/src/auth0-session/index.ts b/src/auth0-session/index.ts
index 4882c4daf..a0378993b 100644
--- a/src/auth0-session/index.ts
+++ b/src/auth0-session/index.ts
@@ -9,10 +9,18 @@ export { StatelessSession } from './session/stateless-session';
 export { AbstractSession, SessionPayload } from './session/abstract-session';
 export { StatefulSession, SessionStore } from './session/stateful-session';
 export { default as TransientStore } from './transient-store';
-export { Config, SessionConfig, CookieConfig, LoginOptions, LogoutOptions, AuthorizationParameters } from './config';
+export {
+  Config,
+  GetConfig,
+  SessionConfig,
+  CookieConfig,
+  LoginOptions,
+  LogoutOptions,
+  AuthorizationParameters
+} from './config';
 export { get as getConfig, ConfigParameters, DeepPartial } from './get-config';
 export { default as loginHandler, HandleLogin } from './handlers/login';
 export { default as logoutHandler, HandleLogout } from './handlers/logout';
 export { default as callbackHandler, CallbackOptions, AfterCallback, HandleCallback } from './handlers/callback';
-export { TokenEndpointResponse, AbstractClient } from './client/abstract-client';
+export { TokenEndpointResponse, AbstractClient, Telemetry } from './client/abstract-client';
 export { SessionCache } from './session-cache';
diff --git a/src/auth0-session/session-cache.ts b/src/auth0-session/session-cache.ts
index d59fd9068..16424949e 100644
--- a/src/auth0-session/session-cache.ts
+++ b/src/auth0-session/session-cache.ts
@@ -5,5 +5,5 @@ export interface SessionCache<Req = any, Res = any, Session = { [key: string]: a
   delete(req: Req, res: Res): Promise<void>;
   isAuthenticated(req: Req, res: Res): Promise<boolean>;
   getIdToken(req: Req, res: Res): Promise<string | undefined>;
-  fromTokenEndpointResponse(tokenSet: TokenEndpointResponse): Session;
+  fromTokenEndpointResponse(req: Req, res: Res, tokenSet: TokenEndpointResponse): Promise<Session>;
 }
diff --git a/src/auth0-session/session/abstract-session.ts b/src/auth0-session/session/abstract-session.ts
index 17bd9a8cc..7b864f080 100644
--- a/src/auth0-session/session/abstract-session.ts
+++ b/src/auth0-session/session/abstract-session.ts
@@ -36,7 +36,7 @@ const assert = (bool: boolean, msg: string) => {
 };
 
 export abstract class AbstractSession<Session> {
-  protected getConfig: () => Config | Promise<Config>;
+  protected getConfig: (req: Auth0RequestCookies) => Config | Promise<Config>;
 
   constructor(getConfig: GetConfig) {
     this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
@@ -62,7 +62,7 @@ export abstract class AbstractSession<Session> {
   ): Promise<void>;
 
   public async read(req: Auth0RequestCookies): Promise<[Session?, number?]> {
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const { rollingDuration, absoluteDuration } = config.session;
 
     try {
@@ -100,7 +100,7 @@ export abstract class AbstractSession<Session> {
     session: Session | null | undefined,
     createdAt?: number
   ): Promise<void> {
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const {
       cookie: { transient, ...cookieConfig }
     } = config.session;
diff --git a/src/auth0-session/session/stateful-session.ts b/src/auth0-session/session/stateful-session.ts
index e3b64561a..8590a9642 100644
--- a/src/auth0-session/session/stateful-session.ts
+++ b/src/auth0-session/session/stateful-session.ts
@@ -4,6 +4,7 @@ import { AbstractSession, SessionPayload } from './abstract-session';
 import { generateCookieValue, getCookieValue } from '../utils/signed-cookies';
 import { signing } from '../utils/hkdf';
 import { Auth0RequestCookies, Auth0ResponseCookies } from '../http';
+import { Config } from '../config';
 
 const debug = createDebug('stateful-session');
 
@@ -30,17 +31,15 @@ export class StatefulSession<
   private keys?: Uint8Array[];
   private store?: SessionStore<Session>;
 
-  private async getStore(): Promise<SessionStore<Session>> {
+  private async getStore(config: Config): Promise<SessionStore<Session>> {
     if (!this.store) {
-      const config = await this.getConfig();
       this.store = config.session.store as SessionStore<Session>;
     }
     return this.store;
   }
 
-  private async getKeys(): Promise<Uint8Array[]> {
+  private async getKeys(config: Config): Promise<Uint8Array[]> {
     if (!this.keys) {
-      const config = await this.getConfig();
       const secret = config.secret;
       const secrets = Array.isArray(secret) ? secret : [secret];
       this.keys = await Promise.all(secrets.map(signing));
@@ -49,14 +48,14 @@ export class StatefulSession<
   }
 
   async getSession(req: Auth0RequestCookies): Promise<SessionPayload<Session> | undefined | null> {
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const { name: sessionName } = config.session;
     const cookies = req.getCookies();
-    const keys = await this.getKeys();
+    const keys = await this.getKeys(config);
     const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);
 
     if (sessionId) {
-      const store = await this.getStore();
+      const store = await this.getStore(config);
       debug('reading session from %s store', sessionId);
       return store.get(sessionId);
     }
@@ -73,11 +72,11 @@ export class StatefulSession<
     cookieOptions: CookieSerializeOptions,
     isNewSession: boolean
   ): Promise<void> {
-    const config = await this.getConfig();
-    const store = await this.getStore();
+    const config = await this.getConfig(req);
+    const store = await this.getStore(config);
     const { name: sessionName, genId } = config.session;
     const cookies = req.getCookies();
-    const keys = await this.getKeys();
+    const keys = await this.getKeys(config);
     let sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);
 
     // If this is a new session created by a new login we need to remove the old session
@@ -106,14 +105,14 @@ export class StatefulSession<
     res: Auth0ResponseCookies,
     cookieOptions: CookieSerializeOptions
   ): Promise<void> {
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const { name: sessionName } = config.session;
     const cookies = req.getCookies();
-    const keys = await this.getKeys();
+    const keys = await this.getKeys(config);
     const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);
 
     if (sessionId) {
-      const store = await this.getStore();
+      const store = await this.getStore(config);
       debug('deleting session %o', sessionId);
       res.clearCookie(sessionName, cookieOptions);
       await store.delete(sessionId);
diff --git a/src/auth0-session/session/stateless-session.ts b/src/auth0-session/session/stateless-session.ts
index 31f2f2d1e..fc4fb6787 100644
--- a/src/auth0-session/session/stateless-session.ts
+++ b/src/auth0-session/session/stateless-session.ts
@@ -24,9 +24,8 @@ export class StatelessSession<
     super(config);
   }
 
-  private async getChunkSize(): Promise<number> {
+  private async getChunkSize(config: Config): Promise<number> {
     if (this.chunkSize === undefined) {
-      const config = await this.getConfig();
       const {
         cookie: { transient, ...cookieConfig },
         name: sessionName
@@ -44,9 +43,8 @@ export class StatelessSession<
     return this.chunkSize;
   }
 
-  private async getKeys(): Promise<Uint8Array[]> {
+  public async getKeys(config: Config): Promise<Uint8Array[]> {
     if (!this.keys) {
-      const config = await this.getConfig();
       const secret = config.secret;
       const secrets = Array.isArray(secret) ? secret : [secret];
       this.keys = await Promise.all(secrets.map(encryption));
@@ -54,13 +52,11 @@ export class StatelessSession<
     return this.keys;
   }
 
-  public async encrypt(payload: jose.JWTPayload, { iat, uat, exp }: Header): Promise<string> {
-    const [key] = await this.getKeys();
+  public async encrypt(payload: jose.JWTPayload, { iat, uat, exp }: Header, key: Uint8Array): Promise<string> {
     return await new jose.EncryptJWT({ ...payload }).setProtectedHeader({ alg, enc, uat, iat, exp }).encrypt(key);
   }
 
-  private async decrypt(jwe: string): Promise<jose.JWTDecryptResult> {
-    const keys = await this.getKeys();
+  private async decrypt(jwe: string, keys: Uint8Array[]): Promise<jose.JWTDecryptResult> {
     let err;
     for (const key of keys) {
       try {
@@ -73,7 +69,7 @@ export class StatelessSession<
   }
 
   async getSession(req: Auth0RequestCookies): Promise<SessionPayload<Session> | undefined | null> {
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const { name: sessionName } = config.session;
     const cookies = req.getCookies();
     let existingSessionValue: string | undefined;
@@ -106,7 +102,8 @@ export class StatelessSession<
         .join('');
     }
     if (existingSessionValue) {
-      const { protectedHeader, payload } = await this.decrypt(existingSessionValue);
+      const keys = await this.getKeys(config);
+      const { protectedHeader, payload } = await this.decrypt(existingSessionValue, keys);
       return { header: protectedHeader as unknown as Header, data: payload as Session };
     }
     return;
@@ -121,14 +118,15 @@ export class StatelessSession<
     exp: number,
     cookieOptions: CookieSerializeOptions
   ): Promise<void> {
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const { name: sessionName } = config.session;
     const cookies = req.getCookies();
 
     debug('found session, creating signed session cookie(s) with name %o(.i)', sessionName);
-    const value = await this.encrypt(session, { iat, uat, exp });
+    const [key] = await this.getKeys(config);
+    const value = await this.encrypt(session, { iat, uat, exp }, key);
 
-    const chunkSize = await this.getChunkSize();
+    const chunkSize = await this.getChunkSize(config);
     const chunkCount = Math.ceil(value.length / chunkSize);
 
     const existingCookies = new Set(
@@ -158,7 +156,7 @@ export class StatelessSession<
     res: Auth0ResponseCookies,
     cookieOptions: CookieSerializeOptions
   ): Promise<void> {
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const { name: sessionName } = config.session;
     const cookies = req.getCookies();
 
diff --git a/src/auth0-session/transient-store.ts b/src/auth0-session/transient-store.ts
index ca7fcbdd0..cbe52f296 100644
--- a/src/auth0-session/transient-store.ts
+++ b/src/auth0-session/transient-store.ts
@@ -1,7 +1,7 @@
 import { generateCookieValue, getCookieValue } from './utils/signed-cookies';
 import { signing } from './utils/hkdf';
 import { Config, GetConfig } from './config';
-import { Auth0Request, Auth0Response } from './http';
+import { Auth0Request, Auth0RequestCookies, Auth0Response } from './http';
 
 export interface StoreOptions {
   sameSite?: boolean | 'lax' | 'strict' | 'none';
@@ -11,15 +11,14 @@ export interface StoreOptions {
 export default class TransientStore {
   private keys?: Uint8Array[];
 
-  protected getConfig: () => Config | Promise<Config>;
+  protected getConfig: (req: Auth0RequestCookies) => Config | Promise<Config>;
 
   constructor(getConfig: GetConfig) {
     this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
   }
 
-  private async getKeys(): Promise<Uint8Array[]> {
+  private async getKeys(config: Config): Promise<Uint8Array[]> {
     if (!this.keys) {
-      const config = await this.getConfig();
       const secret = config.secret;
       const secrets = Array.isArray(secret) ? secret : [secret];
       this.keys = await Promise.all(secrets.map(signing));
@@ -31,7 +30,7 @@ export default class TransientStore {
    * Set a cookie with a value or a generated nonce.
    *
    * @param {String} key Cookie name to use.
-   * @param {IncomingMessage} _req Server Request object.
+   * @param {IncomingMessage} req Server Request object.
    * @param {ServerResponse} res Server Response object.
    * @param {Object} opts Options object.
    * @param {String} opts.sameSite SameSite attribute of `None`, `Lax`, or `Strict`. Defaults to `None`.
@@ -41,12 +40,12 @@ export default class TransientStore {
    */
   async save(
     key: string,
-    _req: Auth0Request,
+    req: Auth0Request,
     res: Auth0Response,
     { sameSite = 'none', value }: StoreOptions
   ): Promise<string> {
     const isSameSiteNone = sameSite === 'none';
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const { domain, path, secure } = config.transactionCookie;
     const basicAttr = {
       httpOnly: true,
@@ -54,7 +53,7 @@ export default class TransientStore {
       domain,
       path
     };
-    const [signingKey] = await this.getKeys();
+    const [signingKey] = await this.getKeys(config);
 
     {
       const cookieValue = await generateCookieValue(key, value, signingKey);
@@ -87,10 +86,10 @@ export default class TransientStore {
   async read(key: string, req: Auth0Request, res: Auth0Response): Promise<string | undefined> {
     const cookies = req.getCookies();
     const cookie = cookies[key];
-    const config = await this.getConfig();
+    const config = await this.getConfig(req);
     const cookieConfig = config.transactionCookie;
 
-    const verifyingKeys = await this.getKeys();
+    const verifyingKeys = await this.getKeys(config);
     let value = await getCookieValue(key, cookie, verifyingKeys);
     res.clearCookie(key, cookieConfig);
 
diff --git a/src/config.ts b/src/config.ts
index 358af2548..450d3d335 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,371 +1,11 @@
-import type { Agent } from 'https';
-import type { LoginOptions, AuthorizationParameters as OidcAuthorizationParameters } from './auth0-session/config';
-import { SessionStore } from './auth0-session/session/stateful-session';
-import Session from './session/session';
+import type { Config as BaseConfig } from './auth0-session/config';
 import { DeepPartial, get as getBaseConfig } from './auth0-session/get-config';
+import type { Auth0Request, Auth0RequestCookies } from './auth0-session/http';
 
 /**
  * @category server
  */
-export interface BaseConfig {
-  /**
-   * The secret(s) used to derive an encryption key for the user identity in a session cookie and
-   * to sign the transient cookies used by the login callback.
-   * Provide a single string secret, but if you want to rotate the secret you can provide an array putting
-   * the new secret first.
-   * You can also use the `AUTH0_SECRET` environment variable.
-   */
-  secret: string | Array<string>;
-
-  /**
-   * Object defining application session cookie attributes.
-   */
-  session: SessionConfig;
-
-  /**
-   * Boolean value to enable Auth0's proprietary logout feature.
-   * Since this SDK is for Auth0, it's set to `true` by default.
-   * Set it to `false` if you don't want to use https://auth0.com/docs/api/authentication#logout.
-   * You can also use the `AUTH0_LOGOUT` environment variable.
-   */
-  auth0Logout?: boolean;
-
-  /**
-   *  URL parameters used when redirecting users to the authorization server to log in.
-   *
-   *  If this property is not provided by your application, its default values will be:
-   *
-   * ```js
-   * {
-   *   response_type: 'code',
-   *   scope: 'openid profile email'
-   * }
-   * ```
-   *
-   * New values can be passed in to change what is returned from the authorization server
-   * depending on your specific scenario. Additional custom parameters can be added as well.
-   *
-   * **Note:** You must provide the required parameters if this object is set.
-   *
-   * ```js
-   * {
-   *   response_type: 'code',
-   *   scope: 'openid profile email',
-   *
-   *   // Additional parameters
-   *   acr_value: 'tenant:test-tenant',
-   *   custom_param: 'custom-value'
-   * };
-   * ```
-   */
-  authorizationParams: AuthorizationParameters;
-
-  /**
-   * The root URL for the application router, for example `https://localhost`.
-   * You can also use the `AUTH0_BASE_URL` environment variable.
-   * If you provide a domain, we will prefix it with `https://`. This can be useful when assigning it to
-   * `VERCEL_URL` for Vercel deploys.
-   *
-   * `NEXT_PUBLIC_AUTH0_BASE_URL` will also be checked if `AUTH0_BASE_URL` is not defined.
-   */
-  baseURL: string;
-
-  /**
-   * The Client ID for your application.
-   * You can also use the `AUTH0_CLIENT_ID` environment variable.
-   */
-  clientID: string;
-
-  /**
-   * The Client Secret for your application.
-   * Required when requesting access tokens.
-   * You can also use the `AUTH0_CLIENT_SECRET` environment variable.
-   */
-  clientSecret?: string;
-
-  /**
-   * Integer value for the system clock's tolerance (leeway) in seconds for ID token verification.`
-   * Defaults to `60` seconds.
-   * You can also use the `AUTH0_CLOCK_TOLERANCE` environment variable.
-   */
-  clockTolerance: number;
-
-  /**
-   * Integer value for the HTTP timeout in milliseconds for authentication requests.
-   * Defaults to `5000` ms.
-   * You can also use the `AUTH0_HTTP_TIMEOUT` environment variable.
-   */
-  httpTimeout: number;
-
-  /**
-   * Instance of an HTTP agent for authentication requests.
-   * (This is for the Node.js runtime only)
-   */
-  httpAgent?: Agent;
-
-  /**
-   * Boolean value to opt-out of sending the library and node version to your authorization server
-   * via the `Auth0-Client` header. Defaults to `true`.
-   * You can also use the `AUTH0_ENABLE_TELEMETRY` environment variable.
-   */
-  enableTelemetry: boolean;
-
-  /**
-   * Function that returns an object with URL-safe state values for login.
-   * Used for passing custom state parameters to your authorization server.
-   * Can also be passed in to {@link HandleLogin}.
-   *
-   * ```js
-   * {
-   *   ...
-   *   getLoginState(options) {
-   *     return {
-   *       returnTo: options.returnTo || req.originalUrl,
-   *       customState: 'foo'
-   *     };
-   *   }
-   * }
-   * ```
-   */
-  getLoginState: (options: LoginOptions) => Record<string, any>;
-
-  /**
-   * Array value of claims to remove from the ID token before storing the cookie session.
-   * Defaults to `['aud', 'iss', 'iat', 'exp', 'nbf', 'nonce', 'azp', 'auth_time', 's_hash', 'at_hash', 'c_hash']`.
-   * You can also use the `AUTH0_IDENTITY_CLAIM_FILTER` environment variable.
-   */
-  identityClaimFilter: string[];
-
-  /**
-   * Boolean value to log the user out from the identity provider on application logout. Defaults to `true`.
-   * You can also use the `AUTH0_IDP_LOGOUT` environment variable.
-   */
-  idpLogout: boolean;
-
-  /**
-   * String value for the expected ID token algorithm. Defaults to 'RS256'.
-   * You can also use the `AUTH0_ID_TOKEN_SIGNING_ALG` environment variable.
-   */
-  idTokenSigningAlg: string;
-
-  /**
-   * **REQUIRED** The root URL for the token issuer with no trailing slash.
-   * This is `https://` plus your Auth0 domain.
-   * You can also use the `AUTH0_ISSUER_BASE_URL` environment variable.
-   */
-  issuerBaseURL: string;
-
-  /**
-   * Set a fallback cookie with no `SameSite` attribute when `response_mode` is `form_post`.
-   * The default `response_mode` for this SDK is `query` so this defaults to `false`
-   * You can also use the `AUTH0_LEGACY_SAME_SITE_COOKIE` environment variable.
-   */
-  legacySameSiteCookie: boolean;
-
-  /**
-   * Boolean value to automatically install the login and logout routes.
-   */
-  routes: {
-    /**
-     * Either a relative path to the application or a valid URI to an external domain.
-     * This value must be registered on the authorization server.
-     * The user will be redirected to this after a logout has been performed.
-     * You can also use the `AUTH0_POST_LOGOUT_REDIRECT` environment variable.
-     */
-    postLogoutRedirect: string;
-
-    /**
-     * Relative path to the application callback to process the response from the authorization server.
-     * Defaults to `/api/auth/callback`.
-     * You can also use the `AUTH0_CALLBACK` environment variable.
-     */
-    callback: string;
-  };
-
-  /**
-   * Private key for use with `private_key_jwt` clients.
-   * This should be a string that is the contents of a PEM file.
-   * You can also use the `AUTH0_CLIENT_ASSERTION_SIGNING_KEY` environment variable.
-   *
-   * For Edge runtime, you can also provide an instance of `CryptoKey`.
-   */
-  clientAssertionSigningKey?: string | CryptoKey;
-
-  /**
-   * The algorithm to sign the client assertion JWT.
-   * Uses one of `token_endpoint_auth_signing_alg_values_supported` if not specified.
-   * If the Authorization Server discovery document does not list `token_endpoint_auth_signing_alg_values_supported`
-   * this property will be required.
-   *  You can also use the `AUTH0_CLIENT_ASSERTION_SIGNING_ALG` environment variable.
-   */
-  clientAssertionSigningAlg?: string;
-
-  /**
-   * By default, the transaction cookie takes the same settings as the
-   * session cookie. But you may want to configure the session cookie to be more
-   * secure in a way that would break the OAuth flow's usage of the transaction
-   * cookie (Setting SameSite=Strict for example).
-   *
-   * You can also use:
-   * `AUTH0_TRANSACTION_COOKIE_NAME`
-   * `AUTH0_TRANSACTION_COOKIE_DOMAIN`
-   * `AUTH0_TRANSACTION_COOKIE_PATH`
-   * `AUTH0_TRANSACTION_COOKIE_SAME_SITE`
-   * `AUTH0_TRANSACTION_COOKIE_SECURE`
-   */
-  transactionCookie: Omit<CookieConfig, 'transient' | 'httpOnly'> & { name: string };
-}
-
-/**
- * Configuration parameters used for the application session.
- *
- * @category Server
- */
-export interface SessionConfig {
-  /**
-   * String value for the cookie name used for the internal session.
-   * This value must only include letters, numbers, and underscores.
-   * Defaults to `appSession`.
-   * You can also use the `AUTH0_SESSION_NAME` environment variable.
-   */
-  name: string;
-
-  /**
-   * By default, the session is stateless and stored in an encrypted cookie. But if you want a stateful session
-   * you can provide a store with `get`, `set` and `destroy` methods to store the session on the server.
-   */
-  store?: SessionStore<Session>;
-
-  /**
-   * A Function for generating a session id when using a custom session store.
-   *
-   * **IMPORTANT** If you override this, you must use a suitable value from your platform to
-   * prevent collisions. For example, for Node: `require('crypto').randomBytes(16).toString('hex')`.
-   */
-  genId?: <Req = any, SessionType extends { [key: string]: any } = { [key: string]: any }>(
-    req: Req,
-    session: SessionType
-  ) => string | Promise<string>;
-
-  /**
-   * If you want your session duration to be rolling, resetting everytime the
-   * user is active on your site, set this to `true`. If you want the session
-   * duration to be absolute, where the user gets logged out a fixed time after login
-   * regardless of activity, set this to `false`.
-   * Defaults to `true`.
-   * You can also use the `AUTH0_SESSION_ROLLING` environment variable.
-   */
-  rolling: boolean;
-
-  /**
-   * Integer value, in seconds, for application session rolling duration.
-   * The amount of time for which the user must be idle for then to be logged out.
-   * Should be `false` when rolling is `false`.
-   * Defaults to `86400` seconds (1 day).
-   * You can also use the AUTH0_SESSION_ROLLING_DURATION environment variable.
-   */
-  rollingDuration: number | false;
-
-  /**
-   * Integer value, in seconds, for application absolute rolling duration.
-   * The amount of time after the user has logged in that they will be logged out.
-   * Set this to `false` if you don't want an absolute duration on your session.
-   * Defaults to `604800` seconds (7 days).
-   * You can also use the `AUTH0_SESSION_ABSOLUTE_DURATION` environment variable.
-   */
-  absoluteDuration: boolean | number;
-
-  /**
-   * Boolean value to enable automatic session saving when using rolling sessions.
-   * If this is `false`, you must call `touchSession(req, res)` to update the session.
-   * Defaults to `true`.
-   * You can also use the `AUTH0_SESSION_AUTO_SAVE` environment variable.
-   */
-  autoSave?: boolean;
-
-  /**
-   * Boolean value to store the ID token in the session. Storing it can make the session cookie too
-   * large.
-   * Defaults to `true`.
-   * You can also use the `AUTH0_SESSION_STORE_ID_TOKEN` environment variable.
-   */
-  storeIDToken: boolean;
-
-  cookie: CookieConfig;
-}
-
-/**
- * Configure how the session cookie and transient cookies are stored.
- *
- * @category Server
- */
-export interface CookieConfig {
-  /**
-   * Domain name for the cookie.
-   * You can also use the `AUTH0_COOKIE_DOMAIN` environment variable.
-   */
-  domain?: string;
-
-  /**
-   * Path for the cookie.
-   * Defaults to `/`.
-   * You should change this to be more restrictive if you application shares a domain with other apps.
-   * You can also use the `AUTH0_COOKIE_PATH` environment variable.
-   */
-  path?: string;
-
-  /**
-   * Set to `true` to use a transient cookie (cookie without an explicit expiration).
-   * Defaults to `false`.
-   * You can also use the `AUTH0_COOKIE_TRANSIENT` environment variable.
-   */
-  transient: boolean;
-
-  /**
-   * Flags the cookie to be accessible only by the web server.
-   * Defaults to `true`.
-   * You can also use the `AUTH0_COOKIE_HTTP_ONLY` environment variable.
-   */
-  httpOnly: boolean;
-
-  /**
-   * Marks the cookie to be used over secure channels only.
-   * Defaults to the protocol of {@link BaseConfig.baseURL}.
-   * You can also use the `AUTH0_COOKIE_SECURE` environment variable.
-   */
-  secure?: boolean;
-
-  /**
-   * Value of the SameSite `Set-Cookie` attribute.
-   * Defaults to `lax` but will be adjusted based on {@link AuthorizationParameters.response_type}.
-   * You can also use the `AUTH0_COOKIE_SAME_SITE` environment variable.
-   */
-  sameSite: 'lax' | 'strict' | 'none';
-}
-
-/**
- * Authorization parameters that will be passed to the identity provider on login.
- *
- * The library uses `response_mode: 'query'` and `response_type: 'code'` (with PKCE) by default.
- *
- * @category Server
- */
-export interface AuthorizationParameters extends OidcAuthorizationParameters {
-  /**
-   * A space-separated list of scopes that will be requested during authentication. For example,
-   * `openid profile email offline_access`.
-   * Defaults to `openid profile email`.
-   */
-  scope: string;
-
-  response_mode: 'query' | 'form_post';
-  response_type: 'id_token' | 'code id_token' | 'code';
-}
-
-/**
- * @category server
- */
-export interface NextConfig extends Pick<BaseConfig, 'identityClaimFilter'> {
+export interface NextConfig extends BaseConfig {
   /**
    * Log users in to a specific organization.
    *
@@ -375,11 +15,9 @@ export interface NextConfig extends Pick<BaseConfig, 'identityClaimFilter'> {
    * If your app supports multiple organizations, you should take a look at {@link AuthorizationParams.organization}.
    */
   organization?: string;
-  routes: {
-    callback: string;
+  routes: BaseConfig['routes'] & {
     login: string;
   };
-  session: Pick<SessionConfig, 'storeIDToken'>;
 }
 
 /**
@@ -405,21 +43,21 @@ export interface NextConfig extends Pick<BaseConfig, 'identityClaimFilter'> {
  *
  * ### Required
  *
- * - `AUTH0_SECRET`: See {@link secret}.
- * - `AUTH0_ISSUER_BASE_URL`: See {@link issuerBaseURL}.
- * - `AUTH0_BASE_URL`: See {@link baseURL}.
- * - `AUTH0_CLIENT_ID`: See {@link clientID}.
- * - `AUTH0_CLIENT_SECRET`: See {@link clientSecret}.
+ * - `AUTH0_SECRET`: See {@link BaseConfig.secret}.
+ * - `AUTH0_ISSUER_BASE_URL`: See {@link BaseConfig.issuerBaseURL}.
+ * - `AUTH0_BASE_URL`: See {@link BaseConfig.baseURL}.
+ * - `AUTH0_CLIENT_ID`: See {@link BaseConfig.clientID}.
+ * - `AUTH0_CLIENT_SECRET`: See {@link BaseConfig.clientSecret}.
  *
  * ### Optional
  *
- * - `AUTH0_CLOCK_TOLERANCE`: See {@link clockTolerance}.
- * - `AUTH0_HTTP_TIMEOUT`: See {@link httpTimeout}.
- * - `AUTH0_ENABLE_TELEMETRY`: See {@link enableTelemetry}.
- * - `AUTH0_IDP_LOGOUT`: See {@link idpLogout}.
- * - `AUTH0_ID_TOKEN_SIGNING_ALG`: See {@link idTokenSigningAlg}.
- * - `AUTH0_LEGACY_SAME_SITE_COOKIE`: See {@link legacySameSiteCookie}.
- * - `AUTH0_IDENTITY_CLAIM_FILTER`: See {@link identityClaimFilter}.
+ * - `AUTH0_CLOCK_TOLERANCE`: See {@link BaseConfig.clockTolerance}.
+ * - `AUTH0_HTTP_TIMEOUT`: See {@link BaseConfig.httpTimeout}.
+ * - `AUTH0_ENABLE_TELEMETRY`: See {@link BaseConfig.enableTelemetry}.
+ * - `AUTH0_IDP_LOGOUT`: See {@link BaseConfig.idpLogout}.
+ * - `AUTH0_ID_TOKEN_SIGNING_ALG`: See {@link BaseConfig.idTokenSigningAlg}.
+ * - `AUTH0_LEGACY_SAME_SITE_COOKIE`: See {@link BaseConfig.legacySameSiteCookie}.
+ * - `AUTH0_IDENTITY_CLAIM_FILTER`: See {@link BaseConfig.identityClaimFilter}.
  * - `NEXT_PUBLIC_AUTH0_LOGIN`: See {@link NextConfig.routes}.
  * - `AUTH0_CALLBACK`: See {@link BaseConfig.routes}.
  * - `AUTH0_POST_LOGOUT_REDIRECT`: See {@link BaseConfig.routes}.
@@ -475,7 +113,7 @@ export interface NextConfig extends Pick<BaseConfig, 'identityClaimFilter'> {
  *
  * @category Server
  */
-export type ConfigParameters = DeepPartial<BaseConfig & NextConfig>;
+export type ConfigParameters = DeepPartial<NextConfig>;
 
 /**
  * @ignore
@@ -505,14 +143,7 @@ const array = (param?: string): string[] | undefined =>
 /**
  * @ignore
  */
-export const getLoginUrl = (): string => {
-  return process.env.NEXT_PUBLIC_AUTH0_LOGIN || '/api/auth/login';
-};
-
-/**
- * @ignore
- */
-export const getConfig = (params: ConfigParameters = {}): { baseConfig: BaseConfig; nextConfig: NextConfig } => {
+export const getConfig = (params: ConfigParameters = {}): NextConfig => {
   // Don't use destructuring here so that the `DefinePlugin` can replace any env vars specified in `next.config.js`
   const AUTH0_SECRET = process.env.AUTH0_SECRET;
   const AUTH0_ISSUER_BASE_URL = process.env.AUTH0_ISSUER_BASE_URL;
@@ -618,15 +249,24 @@ export const getConfig = (params: ConfigParameters = {}): { baseConfig: BaseConf
     }
   });
 
-  const nextConfig: NextConfig = {
+  return {
+    ...baseConfig,
+    organization: organization || AUTH0_ORGANIZATION,
     routes: {
       ...baseConfig.routes,
-      login: baseParams.routes?.login || getLoginUrl()
-    },
-    identityClaimFilter: baseConfig.identityClaimFilter,
-    organization: organization || AUTH0_ORGANIZATION,
-    session: { storeIDToken: baseConfig.session.storeIDToken }
+      login: baseParams.routes?.login || process.env.NEXT_PUBLIC_AUTH0_LOGIN || '/api/auth/login'
+    }
   };
+};
+
+export type GetConfig = (req: Auth0Request | Auth0RequestCookies) => Promise<NextConfig> | NextConfig;
 
-  return { baseConfig, nextConfig };
+export const configSingletonGetter = (params: ConfigParameters = {}, genId: () => string): GetConfig => {
+  let config: NextConfig;
+  return () => {
+    if (!config) {
+      config = getConfig({ ...params, session: { genId, ...params.session } });
+    }
+    return config;
+  };
 };
diff --git a/src/edge.ts b/src/edge.ts
index fe06e5bf9..4e18fd815 100644
--- a/src/edge.ts
+++ b/src/edge.ts
@@ -8,18 +8,14 @@ import {
   HandleLogin,
   HandleLogout,
   HandleProfile,
-  SessionCache,
   TouchSession,
   UpdateSession,
   WithApiAuthRequired,
-  WithPageAuthRequired,
-  telemetry
+  WithPageAuthRequired
 } from './shared';
 import { _initAuth } from './init';
 import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check';
-import { getConfig, getLoginUrl } from './config';
-import { withPageAuthRequiredFactory } from './helpers';
-import { EdgeClient } from './auth0-session/client/edge-client';
+import { clientGetter } from './auth0-session/client/edge-client';
 import { WithMiddlewareAuthRequired } from './helpers/with-middleware-auth-required';
 
 const genId = () => {
@@ -30,7 +26,7 @@ const genId = () => {
     .join('');
 };
 
-let instance: Auth0Server & { sessionCache: SessionCache };
+let instance: Auth0Server;
 
 /**
  * Initialise your own instance of the SDK.
@@ -42,34 +38,29 @@ let instance: Auth0Server & { sessionCache: SessionCache };
 export type InitAuth0 = (params?: ConfigParameters) => Auth0Server;
 
 // For using managed instance with named exports.
-function getInstance(): Auth0Server & { sessionCache: SessionCache } {
+function getInstance(): Auth0Server {
   setIsUsingNamedExports();
   if (instance) {
     return instance;
   }
-  const { baseConfig, nextConfig } = getConfig({ session: { genId } });
-  const client = new EdgeClient(baseConfig, telemetry);
-  instance = _initAuth({ baseConfig, nextConfig, client });
+  instance = _initAuth({ genId, clientGetter });
   return instance;
 }
 
 // For creating own instance.
 export const initAuth0: InitAuth0 = (params) => {
   setIsUsingOwnInstance();
-  const { baseConfig, nextConfig } = getConfig({ ...params, session: { genId, ...params?.session } });
-  const client = new EdgeClient(baseConfig, telemetry);
-  const { sessionCache, ...publicApi } = _initAuth({ baseConfig, nextConfig, client });
-  return publicApi;
+  return _initAuth({ genId, clientGetter, params });
 };
 
-const getSessionCache = () => getInstance().sessionCache;
 export const getSession: GetSession = (...args) => getInstance().getSession(...args);
 export const updateSession: UpdateSession = (...args) => getInstance().updateSession(...args);
 export const getAccessToken: GetAccessToken = (...args) => getInstance().getAccessToken(...args);
 export const touchSession: TouchSession = (...args) => getInstance().touchSession(...args);
 export const withApiAuthRequired: WithApiAuthRequired = (...args) =>
   (getInstance().withApiAuthRequired as any)(...args);
-export const withPageAuthRequired: WithPageAuthRequired = withPageAuthRequiredFactory(getLoginUrl(), getSessionCache);
+export const withPageAuthRequired: WithPageAuthRequired = ((...args: Parameters<WithPageAuthRequired>) =>
+  getInstance().withPageAuthRequired(...args)) as WithPageAuthRequired;
 export const handleLogin: HandleLogin = ((...args: Parameters<HandleLogin>) =>
   getInstance().handleLogin(...args)) as HandleLogin;
 export const handleLogout: HandleLogout = ((...args: Parameters<HandleLogout>) =>
diff --git a/src/handlers/callback.ts b/src/handlers/callback.ts
index b38c874c4..f558eaa2d 100644
--- a/src/handlers/callback.ts
+++ b/src/handlers/callback.ts
@@ -8,10 +8,9 @@ import {
 } from '../auth0-session';
 import { Session } from '../session';
 import { assertReqRes } from '../utils/assert';
-import { BaseConfig, NextConfig } from '../config';
+import { GetConfig, NextConfig } from '../config';
 import { CallbackHandlerError, HandlerErrorCause } from '../utils/errors';
 import { Auth0NextApiRequest, Auth0NextApiResponse, Auth0NextRequest, Auth0NextResponse } from '../http';
-import { LoginOptions } from './login';
 import { AppRouteHandlerFnContext, AuthHandler, getHandler, Handler, OptionsProvider } from './router-helpers';
 
 /**
@@ -278,9 +277,9 @@ export type CallbackHandler = Handler<CallbackOptions>;
 /**
  * @ignore
  */
-export default function handleCallbackFactory(handler: BaseHandleCallback, config: NextConfig): HandleCallback {
-  const appRouteHandler = appRouteHandlerFactory(handler, config);
-  const pageRouteHandler = pageRouteHandlerFactory(handler, config);
+export default function handleCallbackFactory(handler: BaseHandleCallback, getConfig: GetConfig): HandleCallback {
+  const appRouteHandler = appRouteHandlerFactory(handler, getConfig);
+  const pageRouteHandler = pageRouteHandlerFactory(handler, getConfig);
 
   return getHandler<CallbackOptions>(appRouteHandler, pageRouteHandler) as HandleCallback;
 }
@@ -303,7 +302,7 @@ const applyOptions = (
           if (session.user.org_id !== organization) {
             throw new Error(
               `Organization Id (org_id) claim value mismatch in the ID token; ` +
-              `expected "${organization}", found "${session.user.org_id}"`
+                `expected "${organization}", found "${session.user.org_id}"`
             );
           }
         } else {
@@ -313,7 +312,7 @@ const applyOptions = (
           if (session.user.org_name !== organization.toLowerCase()) {
             throw new Error(
               `Organization Name (org_name) claim value mismatch in the ID token; ` +
-              `expected "${organization}", found "${session.user.org_name}"`
+                `expected "${organization}", found "${session.user.org_name}"`
             );
           }
         }
@@ -338,13 +337,15 @@ const applyOptions = (
  */
 const appRouteHandlerFactory: (
   handler: BaseHandleLogin,
-  config: NextConfig
+  getConfig: GetConfig
 ) => (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: CallbackOptions) => Promise<Response> | Response =
-  (handler, config) =>
+  (handler, getConfig) =>
   async (req, _ctx, options = {}) => {
     try {
+      const auth0Req = new Auth0NextRequest(req);
+      const nextConfig = await getConfig(auth0Req);
       const auth0Res = new Auth0NextResponse(new NextResponse());
-      await handler(new Auth0NextRequest(req), auth0Res, applyOptions(req, undefined, options, config));
+      await handler(auth0Req, auth0Res, applyOptions(req, undefined, options, nextConfig));
       return auth0Res.res;
     } catch (e) {
       throw new CallbackHandlerError(e as HandlerErrorCause);
@@ -356,17 +357,15 @@ const appRouteHandlerFactory: (
  */
 const pageRouteHandlerFactory: (
   handler: BaseHandleCallback,
-  config: NextConfig
+  getConfig: GetConfig
 ) => (req: NextApiRequest, res: NextApiResponse, options?: CallbackOptions) => Promise<void> =
-  (handler, config) =>
+  (handler, getConfig) =>
   async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise<void> => {
     try {
+      const auth0Req = new Auth0NextApiRequest(req);
+      const nextConfig = await getConfig(auth0Req);
       assertReqRes(req, res);
-      return await handler(
-        new Auth0NextApiRequest(req),
-        new Auth0NextApiResponse(res),
-        applyOptions(req, res, options, config)
-      );
+      return await handler(auth0Req, new Auth0NextApiResponse(res), applyOptions(req, res, options, nextConfig));
     } catch (e) {
       throw new CallbackHandlerError(e as HandlerErrorCause);
     }
diff --git a/src/handlers/login.ts b/src/handlers/login.ts
index 3e71550e8..c07e48774 100644
--- a/src/handlers/login.ts
+++ b/src/handlers/login.ts
@@ -7,7 +7,7 @@ import {
 } from '../auth0-session';
 import toSafeRedirect from '../utils/url-helpers';
 import { assertReqRes } from '../utils/assert';
-import { BaseConfig, NextConfig } from '../config';
+import { GetConfig, NextConfig } from '../config';
 import { HandlerErrorCause, LoginHandlerError } from '../utils/errors';
 import { Auth0NextApiRequest, Auth0NextApiResponse, Auth0NextRequest, Auth0NextResponse } from '../http';
 import { AppRouteHandlerFnContext, getHandler, OptionsProvider, Handler, AuthHandler } from './router-helpers';
@@ -259,13 +259,9 @@ export type LoginHandler = Handler<LoginOptions>;
 /**
  * @ignore
  */
-export default function handleLoginFactory(
-  handler: BaseHandleLogin,
-  nextConfig: NextConfig,
-  baseConfig: BaseConfig
-): HandleLogin {
-  const appRouteHandler = appRouteHandlerFactory(handler, nextConfig, baseConfig);
-  const pageRouteHandler = pageRouteHandlerFactory(handler, nextConfig, baseConfig);
+export default function handleLoginFactory(handler: BaseHandleLogin, getConfig: GetConfig): HandleLogin {
+  const appRouteHandler = appRouteHandlerFactory(handler, getConfig);
+  const pageRouteHandler = pageRouteHandlerFactory(handler, getConfig);
 
   return getHandler<LoginOptions>(appRouteHandler, pageRouteHandler) as HandleLogin;
 }
@@ -277,22 +273,21 @@ const applyOptions = (
   req: NextApiRequest | NextRequest,
   options: LoginOptions,
   dangerousReturnTo: string | undefined | null,
-  nextConfig: NextConfig,
-  baseConfig: BaseConfig
+  config: NextConfig
 ): BaseLoginOptions => {
   let opts: BaseLoginOptions;
   let getLoginState: GetLoginState | undefined;
   // eslint-disable-next-line prefer-const
   ({ getLoginState, ...opts } = options);
   if (dangerousReturnTo) {
-    const safeBaseUrl = new URL(options.authorizationParams?.redirect_uri || baseConfig.baseURL);
+    const safeBaseUrl = new URL(options.authorizationParams?.redirect_uri || config.baseURL);
     const returnTo = toSafeRedirect(dangerousReturnTo, safeBaseUrl);
     opts = { ...opts, returnTo };
   }
-  if (nextConfig.organization) {
+  if (config.organization) {
     opts = {
       ...opts,
-      authorizationParams: { organization: nextConfig.organization, ...opts.authorizationParams }
+      authorizationParams: { organization: config.organization, ...opts.authorizationParams }
     };
   }
   if (getLoginState) {
@@ -306,21 +301,18 @@ const applyOptions = (
  */
 const appRouteHandlerFactory: (
   handler: BaseHandleLogin,
-  nextConfig: NextConfig,
-  baseConfig: BaseConfig
+  getConfig: GetConfig
 ) => (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: LoginOptions) => Promise<Response> | Response =
-  (handler, nextConfig, baseConfig) =>
+  (handler, getConfig) =>
   async (req, _ctx, options = {}) => {
     try {
+      const auth0Req = new Auth0NextRequest(req);
+      const config = await getConfig(auth0Req);
       const url = new URL(req.url);
       const dangerousReturnTo = url.searchParams.get('returnTo');
 
       const auth0Res = new Auth0NextResponse(new NextResponse());
-      await handler(
-        new Auth0NextRequest(req),
-        auth0Res,
-        applyOptions(req, options, dangerousReturnTo, nextConfig, baseConfig) as BaseLoginOptions
-      );
+      await handler(auth0Req, auth0Res, applyOptions(req, options, dangerousReturnTo, config) as BaseLoginOptions);
       return auth0Res.res;
     } catch (e) {
       throw new LoginHandlerError(e as HandlerErrorCause);
@@ -332,20 +324,21 @@ const appRouteHandlerFactory: (
  */
 const pageRouteHandlerFactory: (
   handler: BaseHandleLogin,
-  nextConfig: NextConfig,
-  baseConfig: BaseConfig
+  getConfig: GetConfig
 ) => (req: NextApiRequest, res: NextApiResponse, options?: LoginOptions) => Promise<void> | void =
-  (handler, nextConfig, baseConfig) =>
+  (handler, getConfig) =>
   async (req, res, options = {}) => {
     try {
+      const auth0Req = new Auth0NextApiRequest(req);
+      const config = await getConfig(auth0Req);
       assertReqRes(req, res);
       const dangerousReturnTo =
         req.query.returnTo && Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo;
 
       return await handler(
-        new Auth0NextApiRequest(req),
+        auth0Req,
         new Auth0NextApiResponse(res),
-        applyOptions(req, options, dangerousReturnTo, nextConfig, baseConfig) as BaseLoginOptions
+        applyOptions(req, options, dangerousReturnTo, config) as BaseLoginOptions
       );
     } catch (e) {
       throw new LoginHandlerError(e as HandlerErrorCause);
diff --git a/src/handlers/logout.ts b/src/handlers/logout.ts
index 517382e11..c4f12ba8d 100644
--- a/src/handlers/logout.ts
+++ b/src/handlers/logout.ts
@@ -4,7 +4,6 @@ import { HandleLogin as BaseHandleLogin, HandleLogout as BaseHandleLogout } from
 import { assertReqRes } from '../utils/assert';
 import { HandlerErrorCause, LogoutHandlerError } from '../utils/errors';
 import { Auth0NextApiRequest, Auth0NextApiResponse, Auth0NextRequest, Auth0NextResponse } from '../http';
-import { BaseConfig } from '../config';
 import { AppRouteHandlerFnContext, AuthHandler, Handler, getHandler, OptionsProvider } from './router-helpers';
 
 /**
diff --git a/src/handlers/profile.ts b/src/handlers/profile.ts
index 3d12d80ce..48a09dc74 100644
--- a/src/handlers/profile.ts
+++ b/src/handlers/profile.ts
@@ -1,10 +1,12 @@
 import { NextApiResponse, NextApiRequest } from 'next';
 import { NextRequest, NextResponse } from 'next/server';
-import { AbstractClient } from '../auth0-session';
 import { SessionCache, Session, fromJson, GetAccessToken } from '../session';
 import { assertReqRes } from '../utils/assert';
 import { ProfileHandlerError, HandlerErrorCause } from '../utils/errors';
 import { AppRouteHandlerFnContext, AuthHandler, getHandler, Handler, OptionsProvider } from './router-helpers';
+import { GetClient } from '../auth0-session/client/abstract-client';
+import { GetConfig } from '../config';
+import { Auth0NextApiRequest, Auth0NextRequest } from '../http';
 
 /**
  * After refetch handler for page router {@link AfterRefetchPageRoute} and app router {@link AfterRefetchAppRoute}.
@@ -124,12 +126,13 @@ export type ProfileHandler = Handler<ProfileOptions>;
  * @ignore
  */
 export default function profileHandler(
-  client: AbstractClient,
+  getConfig: GetConfig,
+  getClient: GetClient,
   getAccessToken: GetAccessToken,
   sessionCache: SessionCache
 ): HandleProfile {
-  const appRouteHandler = appRouteHandlerFactory(client, getAccessToken, sessionCache);
-  const pageRouteHandler = pageRouteHandlerFactory(client, getAccessToken, sessionCache);
+  const appRouteHandler = appRouteHandlerFactory(getConfig, getClient, getAccessToken, sessionCache);
+  const pageRouteHandler = pageRouteHandlerFactory(getConfig, getClient, getAccessToken, sessionCache);
 
   return getHandler<ProfileOptions>(appRouteHandler, pageRouteHandler) as HandleProfile;
 }
@@ -138,13 +141,16 @@ export default function profileHandler(
  * @ignore
  */
 const appRouteHandlerFactory: (
-  client: AbstractClient,
+  getConfig: GetConfig,
+  getClient: GetClient,
   getAccessToken: GetAccessToken,
   sessionCache: SessionCache
 ) => (req: NextRequest, ctx: AppRouteHandlerFnContext, options?: ProfileOptions) => Promise<Response> | Response =
-  (client, getAccessToken, sessionCache) =>
+  (getConfig, getClient, getAccessToken, sessionCache) =>
   async (req, _ctx, options = {}) => {
     try {
+      const config = await getConfig(new Auth0NextRequest(req));
+      const client = await getClient(config);
       const res = new NextResponse();
 
       if (!(await sessionCache.isAuthenticated(req, res))) {
@@ -189,14 +195,17 @@ const appRouteHandlerFactory: (
  * @ignore
  */
 const pageRouteHandlerFactory: (
-  client: AbstractClient,
+  getConfig: GetConfig,
+  getClient: GetClient,
   getAccessToken: GetAccessToken,
   sessionCache: SessionCache
 ) => (req: NextApiRequest, res: NextApiResponse, options?: ProfileOptions) => Promise<void> =
-  (client, getAccessToken, sessionCache) =>
+  (getConfig, getClient, getAccessToken, sessionCache) =>
   async (req: NextApiRequest, res: NextApiResponse, options = {}): Promise<void> => {
     try {
       assertReqRes(req, res);
+      const config = await getConfig(new Auth0NextApiRequest(req));
+      const client = await getClient(config);
 
       if (!(await sessionCache.isAuthenticated(req, res))) {
         res.status(204).end();
diff --git a/src/helpers/testing.ts b/src/helpers/testing.ts
index 400da27d1..703e3890c 100644
--- a/src/helpers/testing.ts
+++ b/src/helpers/testing.ts
@@ -26,8 +26,9 @@ export const generateSessionCookie = async (
 ): Promise<string> => {
   const weekInSeconds = 7 * 24 * 60 * 60;
   const { secret, duration: absoluteDuration = weekInSeconds, ...cookie } = config;
-  const cookieStoreConfig = { secret, session: { absoluteDuration, cookie } };
-  const cookieStore = new StatelessSession(cookieStoreConfig as BaseConfig);
+  const cookieStoreConfig = { secret, session: { absoluteDuration, cookie } } as BaseConfig;
+  const cookieStore = new StatelessSession(cookieStoreConfig);
   const epoch = (Date.now() / 1000) | 0;
-  return cookieStore.encrypt(session, { iat: epoch, uat: epoch, exp: epoch + absoluteDuration });
+  const [key] = await cookieStore.getKeys(cookieStoreConfig);
+  return cookieStore.encrypt(session, { iat: epoch, uat: epoch, exp: epoch + absoluteDuration }, key);
 };
diff --git a/src/helpers/with-middleware-auth-required.ts b/src/helpers/with-middleware-auth-required.ts
index 2a0ce6730..450b9c2fa 100644
--- a/src/helpers/with-middleware-auth-required.ts
+++ b/src/helpers/with-middleware-auth-required.ts
@@ -1,5 +1,7 @@
 import { NextMiddleware, NextRequest, NextResponse } from 'next/server';
 import { SessionCache } from '../session';
+import { GetConfig } from '../config';
+import { Auth0NextRequest } from '../http';
 
 /**
  * Pass custom options to {@link WithMiddlewareAuthRequired}.
@@ -85,12 +87,15 @@ export type WithMiddlewareAuthRequired = (
  * @ignore
  */
 export default function withMiddlewareAuthRequiredFactory(
-  { login, callback }: { login: string; callback: string },
-  getSessionCache: () => SessionCache
+  getConfig: GetConfig,
+  sessionCache: SessionCache
 ): WithMiddlewareAuthRequired {
   return function withMiddlewareAuthRequired(opts?): NextMiddleware {
     return async function wrappedMiddleware(...args) {
       const [req] = args;
+      const {
+        routes: { login, callback }
+      } = await getConfig(new Auth0NextRequest(req));
       let middleware: NextMiddleware | undefined;
       const { pathname, origin, search } = req.nextUrl;
       let returnTo = `${pathname}${search}`;
@@ -105,8 +110,6 @@ export default function withMiddlewareAuthRequiredFactory(
         return;
       }
 
-      const sessionCache = getSessionCache();
-
       const authRes = NextResponse.next();
       const session = await sessionCache.get(req, authRes);
       if (!session?.user) {
diff --git a/src/helpers/with-page-auth-required.ts b/src/helpers/with-page-auth-required.ts
index 7aa33c886..e356bacf3 100644
--- a/src/helpers/with-page-auth-required.ts
+++ b/src/helpers/with-page-auth-required.ts
@@ -3,6 +3,9 @@ import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult
 import { Claims, get, SessionCache } from '../session';
 import { assertCtx } from '../utils/assert';
 import { ParsedUrlQuery } from 'querystring';
+import { GetConfig } from '../config';
+import { Auth0NextRequestCookies } from '../http';
+import { NodeRequest } from '../auth0-session/http';
 
 /**
  * If you wrap your `getServerSideProps` with {@link WithPageAuthRequired} your props object will be augmented with
@@ -180,11 +183,11 @@ export type WithPageAuthRequired = WithPageAuthRequiredPageRouter & WithPageAuth
  * @ignore
  */
 export default function withPageAuthRequiredFactory(
-  loginUrl: string,
-  getSessionCache: () => SessionCache
+  getConfig: GetConfig,
+  sessionCache: SessionCache
 ): WithPageAuthRequired {
-  const appRouteHandler = appRouteHandlerFactory(loginUrl, getSessionCache);
-  const pageRouteHandler = pageRouteHandlerFactory(loginUrl, getSessionCache);
+  const appRouteHandler = appRouteHandlerFactory(getConfig, sessionCache);
+  const pageRouteHandler = pageRouteHandlerFactory(getConfig, sessionCache);
 
   return ((
     fnOrOpts?: WithPageAuthRequiredPageRouterOptions | AppRouterPageRoute,
@@ -201,10 +204,12 @@ export default function withPageAuthRequiredFactory(
  * @ignore
  */
 const appRouteHandlerFactory =
-  (loginUrl: string, getSessionCache: () => SessionCache): WithPageAuthRequiredAppRouter =>
+  (getConfig: GetConfig, sessionCache: SessionCache): WithPageAuthRequiredAppRouter =>
   (handler, opts = {}) =>
   async (params) => {
-    const sessionCache = getSessionCache();
+    const {
+      routes: { login: loginUrl }
+    } = await getConfig(new Auth0NextRequestCookies());
     const [session] = await get({ sessionCache });
     if (!session?.user) {
       const returnTo = typeof opts.returnTo === 'function' ? await opts.returnTo(params) : opts.returnTo;
@@ -219,11 +224,13 @@ const appRouteHandlerFactory =
  * @ignore
  */
 const pageRouteHandlerFactory =
-  (loginUrl: string, getSessionCache: () => SessionCache): WithPageAuthRequiredPageRouter =>
+  (getConfig: GetConfig, sessionCache: SessionCache): WithPageAuthRequiredPageRouter =>
   ({ getServerSideProps, returnTo } = {}) =>
   async (ctx) => {
     assertCtx(ctx);
-    const sessionCache = getSessionCache();
+    const {
+      routes: { login: loginUrl }
+    } = await getConfig(new NodeRequest(ctx.req));
     const session = await sessionCache.get(ctx.req, ctx.res);
     if (!session?.user) {
       return {
diff --git a/src/index.ts b/src/index.ts
index b174483ac..8f5606e2b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,24 +9,20 @@ import {
   HandleLogin,
   HandleLogout,
   HandleProfile,
-  SessionCache,
   TouchSession,
   UpdateSession,
   WithApiAuthRequired,
-  WithPageAuthRequired,
-  telemetry
+  WithPageAuthRequired
 } from './shared';
 import { _initAuth } from './init';
 import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check';
-import { getConfig, getLoginUrl } from './config';
-import { withPageAuthRequiredFactory } from './helpers';
-import { NodeClient } from './auth0-session/client/node-client';
+import { clientGetter } from './auth0-session/client/node-client';
 
 const genId = () => crypto.randomBytes(16).toString('hex');
 
 export type Auth0Server = Omit<Auth0ServerShared, 'withMiddlewareAuthRequired'>;
 
-let instance: Auth0ServerShared & { sessionCache: SessionCache };
+let instance: Auth0ServerShared;
 
 /**
  * Initialise your own instance of the SDK.
@@ -38,34 +34,34 @@ let instance: Auth0ServerShared & { sessionCache: SessionCache };
 export type InitAuth0 = (params?: ConfigParameters) => Omit<Auth0Server, 'withMiddlewareAuthRequired'>;
 
 // For using managed instance with named exports.
-function getInstance(): Auth0ServerShared & { sessionCache: SessionCache } {
+function getInstance(): Auth0ServerShared {
   setIsUsingNamedExports();
   if (instance) {
     return instance;
   }
-  const { baseConfig, nextConfig } = getConfig({ session: { genId } });
-  const client = new NodeClient(baseConfig, telemetry);
-  instance = _initAuth({ baseConfig, nextConfig, client });
+  instance = _initAuth({ genId, clientGetter });
   return instance;
 }
 
 // For creating own instance.
 export const initAuth0: InitAuth0 = (params) => {
   setIsUsingOwnInstance();
-  const { baseConfig, nextConfig } = getConfig({ ...params, session: { genId, ...params?.session } });
-  const client = new NodeClient(baseConfig, telemetry);
-  const { sessionCache, withMiddlewareAuthRequired, ...publicApi } = _initAuth({ baseConfig, nextConfig, client });
+  const { withMiddlewareAuthRequired, ...publicApi } = _initAuth({
+    genId,
+    params,
+    clientGetter
+  });
   return publicApi;
 };
 
-const getSessionCache = () => getInstance().sessionCache;
 export const getSession: GetSession = (...args) => getInstance().getSession(...args);
 export const updateSession: UpdateSession = (...args) => getInstance().updateSession(...args);
 export const getAccessToken: GetAccessToken = (...args) => getInstance().getAccessToken(...args);
 export const touchSession: TouchSession = (...args) => getInstance().touchSession(...args);
 export const withApiAuthRequired: WithApiAuthRequired = (...args) =>
   (getInstance().withApiAuthRequired as any)(...args);
-export const withPageAuthRequired: WithPageAuthRequired = withPageAuthRequiredFactory(getLoginUrl(), getSessionCache);
+export const withPageAuthRequired: WithPageAuthRequired = ((...args: Parameters<WithPageAuthRequired>) =>
+  getInstance().withPageAuthRequired(...args)) as WithPageAuthRequired;
 export const handleLogin: HandleLogin = ((...args: Parameters<HandleLogin>) =>
   getInstance().handleLogin(...args)) as HandleLogin;
 export const handleLogout: HandleLogout = ((...args: Parameters<HandleLogout>) =>
diff --git a/src/init.ts b/src/init.ts
index 73cb5779f..86b718851 100644
--- a/src/init.ts
+++ b/src/init.ts
@@ -1,25 +1,23 @@
 import {
-  StatelessSession,
-  StatefulSession,
   TransientStore,
   loginHandler as baseLoginHandler,
   logoutHandler as baseLogoutHandler,
   callbackHandler as baseCallbackHandler,
-  AbstractClient
+  Telemetry
 } from './auth0-session';
 import { handlerFactory, callbackHandler, loginHandler, logoutHandler, profileHandler } from './handlers';
 import {
   sessionFactory,
   accessTokenFactory,
   SessionCache,
-  Session,
   touchSessionFactory,
   updateSessionFactory
 } from './session/';
 import { withPageAuthRequiredFactory, withApiAuthRequiredFactory } from './helpers';
-import { ConfigParameters, BaseConfig, NextConfig } from './config';
-import { Auth0Server } from './shared';
+import { configSingletonGetter, ConfigParameters } from './config';
+import { Auth0Server, telemetry } from './shared';
 import withMiddlewareAuthRequiredFactory from './helpers/with-middleware-auth-required';
+import { GetClient } from './auth0-session/client/abstract-client';
 
 /**
  * Initialise your own instance of the SDK.
@@ -31,43 +29,40 @@ import withMiddlewareAuthRequiredFactory from './helpers/with-middleware-auth-re
 export type InitAuth0 = (params?: ConfigParameters) => Auth0Server;
 
 export const _initAuth = ({
-  baseConfig,
-  nextConfig,
-  client
+  params,
+  genId,
+  clientGetter
 }: {
-  baseConfig: BaseConfig;
-  nextConfig: NextConfig;
-  client: AbstractClient;
-}): Auth0Server & {
-  sessionCache: SessionCache;
-} => {
+  params?: ConfigParameters;
+  genId: () => string;
+  clientGetter: (telemetry: Telemetry) => GetClient;
+}): Auth0Server => {
+  const getConfig = configSingletonGetter(params, genId);
+  const getClient = clientGetter(telemetry);
+
   // Init base layer (with base config)
-  const transientStore = new TransientStore(baseConfig);
+  const transientStore = new TransientStore(getConfig);
 
-  const sessionStore = baseConfig.session.store
-    ? new StatefulSession<Session>(baseConfig)
-    : new StatelessSession<Session>(baseConfig);
-  const sessionCache = new SessionCache(baseConfig, sessionStore);
-  const baseHandleLogin = baseLoginHandler(baseConfig, client, transientStore);
-  const baseHandleLogout = baseLogoutHandler(baseConfig, client, sessionCache);
-  const baseHandleCallback = baseCallbackHandler(baseConfig, client, sessionCache, transientStore);
+  const sessionCache = new SessionCache(getConfig);
+  const baseHandleLogin = baseLoginHandler(getConfig, getClient, transientStore);
+  const baseHandleLogout = baseLogoutHandler(getConfig, getClient, sessionCache);
+  const baseHandleCallback = baseCallbackHandler(getConfig, getClient, sessionCache, transientStore);
 
   // Init Next layer (with next config)
   const getSession = sessionFactory(sessionCache);
   const touchSession = touchSessionFactory(sessionCache);
   const updateSession = updateSessionFactory(sessionCache);
-  const getAccessToken = accessTokenFactory(nextConfig, client, sessionCache);
+  const getAccessToken = accessTokenFactory(getConfig, getClient, sessionCache);
   const withApiAuthRequired = withApiAuthRequiredFactory(sessionCache);
-  const withPageAuthRequired = withPageAuthRequiredFactory(nextConfig.routes.login, () => sessionCache);
-  const handleLogin = loginHandler(baseHandleLogin, nextConfig, baseConfig);
+  const withPageAuthRequired = withPageAuthRequiredFactory(getConfig, sessionCache);
+  const handleLogin = loginHandler(baseHandleLogin, getConfig);
   const handleLogout = logoutHandler(baseHandleLogout);
-  const handleCallback = callbackHandler(baseHandleCallback, nextConfig);
-  const handleProfile = profileHandler(client, getAccessToken, sessionCache);
+  const handleCallback = callbackHandler(baseHandleCallback, getConfig);
+  const handleProfile = profileHandler(getConfig, getClient, getAccessToken, sessionCache);
   const handleAuth = handlerFactory({ handleLogin, handleLogout, handleCallback, handleProfile });
-  const withMiddlewareAuthRequired = withMiddlewareAuthRequiredFactory(nextConfig.routes, () => sessionCache);
+  const withMiddlewareAuthRequired = withMiddlewareAuthRequiredFactory(getConfig, sessionCache);
 
   return {
-    sessionCache,
     getSession,
     touchSession,
     updateSession,
diff --git a/src/session/cache.ts b/src/session/cache.ts
index 4c7ac7c77..406396fc3 100644
--- a/src/session/cache.ts
+++ b/src/session/cache.ts
@@ -2,7 +2,7 @@ import { IncomingMessage, ServerResponse } from 'http';
 import { NextApiRequest, NextApiResponse } from 'next';
 import { NextRequest, NextResponse } from 'next/server';
 import type { TokenEndpointResponse } from '../auth0-session';
-import { Config, SessionCache as ISessionCache, AbstractSession } from '../auth0-session';
+import { SessionCache as ISessionCache, AbstractSession, StatefulSession, StatelessSession } from '../auth0-session';
 import Session, { fromJson, fromTokenEndpointResponse } from './session';
 import { Auth0Request, Auth0Response, NodeRequest, NodeResponse } from '../auth0-session/http';
 import {
@@ -14,11 +14,12 @@ import {
   Auth0NextResponse
 } from '../http';
 import { isNextApiRequest, isRequest } from '../utils/req-helpers';
+import { GetConfig, NextConfig } from '../config';
 
 type Req = IncomingMessage | NextRequest | NextApiRequest;
 type Res = ServerResponse | NextResponse | NextApiResponse;
 
-const getAuth0ReqRes = (req: Req, res: Res): [Auth0Request, Auth0Response] => {
+export const getAuth0ReqRes = (req: Req, res: Res): [Auth0Request, Auth0Response] => {
   if (isRequest(req)) {
     return [new Auth0NextRequest(req as NextRequest), new Auth0NextResponse(res as NextResponse)];
   }
@@ -31,19 +32,31 @@ const getAuth0ReqRes = (req: Req, res: Res): [Auth0Request, Auth0Response] => {
 export default class SessionCache implements ISessionCache<Req, Res, Session> {
   private cache: WeakMap<Req, Session | null | undefined>;
   private iatCache: WeakMap<Req, number | undefined>;
+  private sessionStore?: AbstractSession<Session>;
 
-  constructor(public config: Config, public sessionStore: AbstractSession<Session>) {
+  constructor(public getConfig: GetConfig) {
     this.cache = new WeakMap();
     this.iatCache = new WeakMap();
   }
 
+  public getSessionStore(config: NextConfig): AbstractSession<Session> {
+    if (!this.sessionStore) {
+      this.sessionStore = config.session.store
+        ? new StatefulSession<Session>(config)
+        : new StatelessSession<Session>(config);
+    }
+    return this.sessionStore;
+  }
+
   private async init(req: Req, res: Res, autoSave = true): Promise<void> {
     if (!this.cache.has(req)) {
       const [auth0Req] = getAuth0ReqRes(req, res);
-      const [json, iat] = await this.sessionStore.read(auth0Req);
+      const config = await this.getConfig(auth0Req);
+      const sessionStore = this.getSessionStore(config);
+      const [json, iat] = await sessionStore.read(auth0Req);
       this.iatCache.set(req, iat);
       this.cache.set(req, fromJson(json));
-      if (this.config.session.rolling && this.config.session.autoSave && autoSave) {
+      if (config.session.rolling && config.session.autoSave && autoSave) {
         await this.save(req, res);
       }
     }
@@ -51,7 +64,9 @@ export default class SessionCache implements ISessionCache<Req, Res, Session> {
 
   async save(req: Req, res: Res): Promise<void> {
     const [auth0Req, auth0Res] = getAuth0ReqRes(req, res);
-    await this.sessionStore.save(auth0Req, auth0Res, this.cache.get(req), this.iatCache.get(req));
+    const config = await this.getConfig(auth0Req);
+    const sessionStore = this.getSessionStore(config);
+    await sessionStore.save(auth0Req, auth0Res, this.cache.get(req), this.iatCache.get(req));
   }
 
   async create(req: Req, res: Res, session: Session): Promise<void> {
@@ -88,8 +103,10 @@ export default class SessionCache implements ISessionCache<Req, Res, Session> {
     return this.cache.get(req);
   }
 
-  fromTokenEndpointResponse(tokenSet: TokenEndpointResponse): Session {
-    return fromTokenEndpointResponse(tokenSet, this.config);
+  async fromTokenEndpointResponse(req: Req, res: Res, tokenSet: TokenEndpointResponse): Promise<Session> {
+    const [auth0Req] = getAuth0ReqRes(req, res);
+    const config = await this.getConfig(auth0Req);
+    return fromTokenEndpointResponse(tokenSet, config);
   }
 }
 
@@ -105,13 +122,12 @@ export const get = async ({
   if (req && res) {
     return [await sessionCache.get(req, res)];
   }
-  const {
-    sessionStore,
-    config: {
-      session: { rolling, autoSave }
-    }
-  } = sessionCache;
   const auth0Req = new Auth0NextRequestCookies();
+  const config = await sessionCache.getConfig(auth0Req);
+  const sessionStore = sessionCache.getSessionStore(config);
+  const {
+    session: { rolling, autoSave }
+  } = config;
   const [session, iat] = await sessionStore.read(auth0Req);
   if (rolling && autoSave) {
     await set({ session, sessionCache, iat });
@@ -131,10 +147,12 @@ export const set = async ({
   iat?: number;
   req?: Req;
   res?: Res;
-}) => {
+}): Promise<void> => {
   if (req && res) {
     return sessionCache.set(req, res, session);
   }
-  const { sessionStore } = sessionCache;
-  await sessionStore.save(new Auth0NextRequestCookies(), new Auth0NextResponseCookies(), session, iat);
+  const auth0Req = new Auth0NextRequestCookies();
+  const config = await sessionCache.getConfig(auth0Req);
+  const sessionStore = sessionCache.getSessionStore(config);
+  await sessionStore.save(auth0Req, new Auth0NextResponseCookies(), session, iat);
 };
diff --git a/src/session/get-access-token.ts b/src/session/get-access-token.ts
index 1995069a0..c4834b4f9 100644
--- a/src/session/get-access-token.ts
+++ b/src/session/get-access-token.ts
@@ -1,11 +1,14 @@
 import { IncomingMessage, ServerResponse } from 'http';
 import { NextApiRequest, NextApiResponse } from 'next';
-import { AbstractClient } from '../auth0-session';
+import { NextRequest, NextResponse } from 'next/server';
+import { AuthorizationParameters } from '../auth0-session';
 import { AccessTokenError, AccessTokenErrorCode } from '../utils/errors';
 import { intersect, match } from '../utils/array';
 import { Session, SessionCache, fromTokenEndpointResponse, get, set } from '../session';
-import { AuthorizationParameters, NextConfig } from '../config';
-import { NextRequest, NextResponse } from 'next/server';
+import { GetClient } from '../auth0-session/client/abstract-client';
+import { GetConfig } from '../config';
+import { getAuth0ReqRes } from './cache';
+import { Auth0NextRequestCookies } from '../http';
 
 /**
  * After refresh handler for page router {@link AfterRefreshPageRoute} and app router {@link AfterRefreshAppRoute}.
@@ -226,13 +229,16 @@ export type GetAccessToken = (
  * @ignore
  */
 export default function accessTokenFactory(
-  config: NextConfig,
-  client: AbstractClient,
+  getConfig: GetConfig,
+  getClient: GetClient,
   sessionCache: SessionCache
 ): GetAccessToken {
   return async (reqOrOpts?, res?, accessTokenRequest?): Promise<GetAccessTokenResult> => {
     const options = (res ? accessTokenRequest : reqOrOpts) as AccessTokenRequest | undefined;
     const req = (res ? reqOrOpts : undefined) as IncomingMessage | NextApiRequest | undefined;
+    // TODO: clean up
+    const config = await getConfig(req ? getAuth0ReqRes(req, res as any)[0] : new Auth0NextRequestCookies());
+    const client = await getClient(config);
 
     const parts = await get({ sessionCache, req, res });
     let [session] = parts;
diff --git a/src/session/session.ts b/src/session/session.ts
index 057334952..b9b50b259 100644
--- a/src/session/session.ts
+++ b/src/session/session.ts
@@ -1,6 +1,5 @@
 import * as jose from 'jose';
 import type { TokenEndpointResponse } from '../auth0-session/client/abstract-client';
-import { Config } from '../auth0-session';
 import { NextConfig } from '../config';
 
 /**
@@ -61,10 +60,7 @@ export default class Session {
 /**
  * @ignore
  */
-export function fromTokenEndpointResponse(
-  tokenEndpointResponse: TokenEndpointResponse,
-  config: Config | NextConfig
-): Session {
+export function fromTokenEndpointResponse(tokenEndpointResponse: TokenEndpointResponse, config: NextConfig): Session {
   // Get the claims without any OIDC-specific claim.
   const claims = jose.decodeJwt(tokenEndpointResponse.id_token as string);
   config.identityClaimFilter.forEach((claim) => {
diff --git a/tests/auth0-session/client/edge-client.test.ts b/tests/auth0-session/client/edge-client.test.ts
index a4c9afb48..5f37bc7a1 100644
--- a/tests/auth0-session/client/edge-client.test.ts
+++ b/tests/auth0-session/client/edge-client.test.ts
@@ -8,7 +8,7 @@ import { jwks, makeIdToken } from '../fixtures/cert';
 import pkg from '../../../package.json';
 import wellKnown from '../fixtures/well-known.json';
 import version from '../../../src/version';
-import { EdgeClient } from '../../../src/auth0-session/client/edge-client';
+import { EdgeClient, clientGetter } from '../../../src/auth0-session/client/edge-client';
 import { mockFetch } from '../../fixtures/app-router-helpers';
 import { Auth0Request } from '../../../src/auth0-session/http';
 import { readFileSync } from 'fs';
@@ -50,14 +50,14 @@ const defaultConfig: ConfigParameters = {
 };
 
 const getClient = async (params: ConfigParameters = {}): Promise<EdgeClient> => {
-  return new EdgeClient(getConfig({ ...defaultConfig, ...params }), {
+  return clientGetter({
     name: 'nextjs-auth0',
     version
-  });
+  })(getConfig({ ...defaultConfig, ...params }));
 };
 
 describe('edge client', function () {
-  let headersSpy = jest.fn();
+  const headersSpy = jest.fn();
 
   beforeEach(() => {
     mockFetch();
@@ -147,7 +147,7 @@ describe('edge client', function () {
       idTokenSigningAlg: 'RS256'
     });
     // @ts-ignore
-    expect((await client.getClient())[1].id_token_signed_response_alg).toEqual('RS256');
+    expect(client.client.id_token_signed_response_alg).toEqual('RS256');
   });
 
   it('should use discovered logout endpoint by default', async function () {
@@ -264,14 +264,10 @@ describe('edge client', function () {
       );
 
     await expect(
-      (
-        await getClient({
-          issuerBaseURL: 'https://op2.example.com',
-          idpLogout: true
-        })
-      )
-        // @ts-ignore
-        .getClient()
+      getClient({
+        issuerBaseURL: 'https://op2.example.com',
+        idpLogout: true
+      })
     ).resolves.not.toThrow();
   });
 
@@ -279,9 +275,7 @@ describe('edge client', function () {
     nock.cleanAll();
     nock('https://op.example.com').get('/.well-known/oauth-authorization-server').reply(500);
     nock('https://op.example.com').get('/.well-known/openid-configuration').reply(500);
-    await expect((await getClient()).userinfo('token')).rejects.toThrow(
-      /Discovery requests failing for https:\/\/op.example.com/
-    );
+    await expect(getClient()).rejects.toThrow(/Discovery requests failing for https:\/\/op.example.com/);
   });
 
   it('should throw UserInfoError when userinfo fails', async () => {
@@ -293,8 +287,7 @@ describe('edge client', function () {
   });
 
   it('should only support code flow', async () => {
-    const client = await getClient({ authorizationParams: { response_type: 'id_token' } });
-    await expect(client.authorizationUrl({})).rejects.toThrow(
+    await expect(getClient({ authorizationParams: { response_type: 'id_token' } })).rejects.toThrow(
       'This SDK only supports `response_type=code` when used in an Edge runtime.'
     );
   });
diff --git a/tests/auth0-session/client/node-client.test.ts b/tests/auth0-session/client/node-client.test.ts
index c68fae9d2..1ba87dbfb 100644
--- a/tests/auth0-session/client/node-client.test.ts
+++ b/tests/auth0-session/client/node-client.test.ts
@@ -4,7 +4,7 @@ import { jwks } from '../fixtures/cert';
 import pkg from '../../../package.json';
 import wellKnown from '../fixtures/well-known.json';
 import version from '../../../src/version';
-import { NodeClient } from '../../../src/auth0-session/client/node-client';
+import { NodeClient, clientGetter } from '../../../src/auth0-session/client/node-client';
 import { UserInfoError } from '../../../src/auth0-session/utils/errors';
 
 const defaultConfig = {
@@ -19,10 +19,10 @@ const defaultConfig = {
 };
 
 const getClient = async (params: ConfigParameters = {}): Promise<NodeClient> => {
-  return new NodeClient(getConfig({ ...defaultConfig, ...params }), {
+  return clientGetter({
     name: 'nextjs-auth0',
     version
-  });
+  })(getConfig({ ...defaultConfig, ...params }));
 };
 
 describe('node client', function () {
@@ -70,17 +70,11 @@ describe('node client', function () {
   });
 
   it('should accept lazy config', async function () {
-    expect(
-      () =>
-        new NodeClient(
-          () => {
-            throw new Error();
-          },
-          {
-            name: 'nextjs-auth0',
-            version
-          }
-        )
+    expect(() =>
+      clientGetter({
+        name: 'nextjs-auth0',
+        version
+      })
     ).not.toThrow();
   });
 
@@ -107,7 +101,7 @@ describe('node client', function () {
       idTokenSigningAlg: 'RS256'
     });
     // @ts-ignore
-    expect((await client.getClient()).id_token_signed_response_alg).toEqual('RS256');
+    expect((await client.client).id_token_signed_response_alg).toEqual('RS256');
   });
 
   it('should use discovered logout endpoint by default', async function () {
@@ -209,14 +203,10 @@ describe('node client', function () {
       );
 
     await expect(
-      (
-        await getClient({
-          issuerBaseURL: 'https://op2.example.com',
-          idpLogout: true
-        })
-      )
-        // @ts-ignore
-        .getClient()
+      getClient({
+        issuerBaseURL: 'https://op2.example.com',
+        idpLogout: true
+      })
     ).resolves.not.toThrow();
   });
 
@@ -224,7 +214,7 @@ describe('node client', function () {
     nock.cleanAll();
     nock('https://op.example.com').get('/.well-known/oauth-authorization-server').reply(500);
     nock('https://op.example.com').get('/.well-known/openid-configuration').reply(500);
-    await expect((await getClient()).userinfo('token')).rejects.toThrow(
+    await expect(getClient).rejects.toThrow(
       'Discovery requests failing for https://op.example.com, expected 200 OK, got: 500 Internal Server Error'
     );
   });
diff --git a/tests/auth0-session/fixtures/server.ts b/tests/auth0-session/fixtures/server.ts
index e99637982..a5c228430 100644
--- a/tests/auth0-session/fixtures/server.ts
+++ b/tests/auth0-session/fixtures/server.ts
@@ -25,7 +25,7 @@ import { cert, key } from './https';
 import { Claims } from '../../../src/session';
 import version from '../../../src/version';
 import { NodeRequest, NodeResponse } from '../../../src/auth0-session/http';
-import { NodeClient } from '../../../src/auth0-session/client/node-client';
+import { clientGetter } from '../../../src/auth0-session/client/node-client';
 
 export type SessionResponse = TokenSetParameters & { claims: Claims };
 
@@ -54,7 +54,11 @@ class TestSessionCache implements SessionCache<IncomingMessage, ServerResponse>
     const [session] = await this.cookieStore.read(new NodeRequest(req));
     return session?.id_token;
   }
-  fromTokenEndpointResponse(tokenSet: TokenSet): { [p: string]: any } {
+  async fromTokenEndpointResponse(
+    _req: IncomingMessage,
+    _res: ServerResponse,
+    tokenSet: TokenSet
+  ): Promise<{ [p: string]: any }> {
     return tokenSet;
   }
 }
@@ -68,15 +72,15 @@ type Handlers = {
 
 const createHandlers = (params: ConfigParameters): Handlers => {
   const config = getConfig(params);
-  const client = new NodeClient(config, { name: 'nextjs-auth0', version });
+  const getClient = clientGetter({ name: 'nextjs-auth0', version });
   const transientStore = new TransientStore(config);
   const cookieStore = params.session?.store ? new StatefulSession<any>(config) : new StatelessSession<any>(config);
   const sessionCache = new TestSessionCache(cookieStore);
 
   return {
-    handleLogin: loginHandler(config, client, transientStore),
-    handleLogout: logoutHandler(config, client, sessionCache),
-    handleCallback: callbackHandler(config, client, sessionCache, transientStore),
+    handleLogin: loginHandler(config, getClient, transientStore),
+    handleLogout: logoutHandler(config, getClient, sessionCache),
+    handleCallback: callbackHandler(config, getClient, sessionCache, transientStore),
     handleSession: async (req: IncomingMessage, res: ServerResponse) => {
       const nodeReq = new NodeRequest(req);
       const [json, iat] = await cookieStore.read(nodeReq);
diff --git a/tests/config.test.ts b/tests/config.test.ts
index 4022bde67..c6dd7bd7c 100644
--- a/tests/config.test.ts
+++ b/tests/config.test.ts
@@ -1,4 +1,4 @@
-import { BaseConfig, NextConfig, getConfig } from '../src/config';
+import { NextConfig, getConfig } from '../src/config';
 
 const getConfigWithEnv = (
   env: any = {},
@@ -10,7 +10,7 @@ const getConfigWithEnv = (
     AUTH0_CLIENT_ID: '__test_client_id__',
     AUTH0_CLIENT_SECRET: '__test_client_secret__'
   }
-): { baseConfig: BaseConfig; nextConfig: NextConfig } => {
+): NextConfig => {
   const bkp = process.env;
   process.env = {
     ...process.env,
@@ -28,8 +28,8 @@ const getConfigWithEnv = (
 
 describe('config params', () => {
   test('should return an object from empty defaults', () => {
-    const { baseConfig, nextConfig } = getConfigWithEnv();
-    expect(baseConfig).toStrictEqual({
+    const nextConfig = getConfigWithEnv();
+    expect(nextConfig).toStrictEqual({
       secret: '__long_super_secret_secret__',
       issuerBaseURL: 'https://example.auth0.com',
       baseURL: 'https://example.com',
@@ -65,7 +65,7 @@ describe('config params', () => {
           sameSite: 'lax'
         }
       },
-      routes: { callback: '/api/auth/callback', postLogoutRedirect: '' },
+      routes: { callback: '/api/auth/callback', postLogoutRedirect: '', login: '/api/auth/login' },
       getLoginState: expect.any(Function),
       identityClaimFilter: [
         'aud',
@@ -87,31 +87,8 @@ describe('config params', () => {
         path: '/',
         sameSite: 'lax',
         secure: true
-      }
-    });
-    expect(nextConfig).toStrictEqual({
-      identityClaimFilter: [
-        'aud',
-        'iss',
-        'iat',
-        'exp',
-        'nbf',
-        'nonce',
-        'azp',
-        'auth_time',
-        's_hash',
-        'at_hash',
-        'c_hash'
-      ],
-      routes: {
-        login: '/api/auth/login',
-        callback: '/api/auth/callback',
-        postLogoutRedirect: ''
       },
-      organization: undefined,
-      session: {
-        storeIDToken: true
-      }
+      organization: undefined
     });
   });
 
@@ -128,7 +105,7 @@ describe('config params', () => {
         AUTH0_COOKIE_SECURE: 'ok',
         AUTH0_SESSION_ABSOLUTE_DURATION: 'no',
         AUTH0_SESSION_STORE_ID_TOKEN: '0'
-      }).baseConfig
+      })
     ).toMatchObject({
       auth0Logout: false,
       enableTelemetry: false,
@@ -149,7 +126,7 @@ describe('config params', () => {
       getConfigWithEnv({
         AUTH0_SESSION_ROLLING_DURATION: 'no',
         AUTH0_SESSION_ROLLING: 'no'
-      }).baseConfig
+      })
     ).toMatchObject({
       session: {
         rolling: false,
@@ -165,7 +142,7 @@ describe('config params', () => {
         AUTH0_HTTP_TIMEOUT: '9999',
         AUTH0_SESSION_ROLLING_DURATION: '0',
         AUTH0_SESSION_ABSOLUTE_DURATION: '1'
-      }).baseConfig
+      })
     ).toMatchObject({
       clockTolerance: 100,
       httpTimeout: 9999,
@@ -181,14 +158,14 @@ describe('config params', () => {
     expect(
       getConfigWithEnv({
         AUTH0_IDENTITY_CLAIM_FILTER: 'claim1,claim2,claim3'
-      }).baseConfig
+      })
     ).toMatchObject({
       identityClaimFilter: ['claim1', 'claim2', 'claim3']
     });
   });
 
   test('passed in arguments should take precedence', () => {
-    const { baseConfig, nextConfig } = getConfigWithEnv(
+    const nextConfig = getConfigWithEnv(
       {
         AUTH0_ORGANIZATION: 'foo'
       },
@@ -212,7 +189,7 @@ describe('config params', () => {
         organization: 'bar'
       }
     );
-    expect(baseConfig).toMatchObject({
+    expect(nextConfig).toMatchObject({
       authorizationParams: {
         audience: 'foo',
         scope: 'openid bar'
@@ -228,9 +205,7 @@ describe('config params', () => {
           transient: false
         },
         name: 'quuuux'
-      }
-    });
-    expect(nextConfig).toMatchObject({
+      },
       organization: 'bar'
     });
   });
@@ -239,7 +214,7 @@ describe('config params', () => {
     expect(
       getConfigWithEnv({
         AUTH0_BASE_URL: 'foo.auth0.com'
-      }).baseConfig
+      })
     ).toMatchObject({
       baseURL: 'https://foo.auth0.com'
     });
@@ -259,7 +234,7 @@ describe('config params', () => {
           AUTH0_CLIENT_ID: '__test_client_id__',
           AUTH0_CLIENT_SECRET: '__test_client_secret__'
         }
-      ).baseConfig
+      )
     ).toMatchObject({
       baseURL: 'https://public-foo.auth0.com'
     });
@@ -270,19 +245,16 @@ describe('config params', () => {
       getConfigWithEnv({
         AUTH0_BASE_URL: 'foo.auth0.com',
         NEXT_PUBLIC_AUTH0_BASE_URL: 'bar.auth0.com'
-      }).baseConfig
+      })
     ).toMatchObject({
       baseURL: 'https://foo.auth0.com'
     });
   });
 
   test('should accept optional callback path', () => {
-    const { baseConfig, nextConfig } = getConfigWithEnv({
+    const nextConfig = getConfigWithEnv({
       AUTH0_CALLBACK: '/api/custom-callback'
     });
-    expect(baseConfig).toMatchObject({
-      routes: expect.objectContaining({ callback: '/api/custom-callback' })
-    });
     expect(nextConfig).toMatchObject({
       routes: expect.objectContaining({ callback: '/api/custom-callback' })
     });
diff --git a/tests/fixtures/app-router-helpers.ts b/tests/fixtures/app-router-helpers.ts
index d84eabe25..f2af60c32 100644
--- a/tests/fixtures/app-router-helpers.ts
+++ b/tests/fixtures/app-router-helpers.ts
@@ -114,7 +114,7 @@ export const getSession = async (config: any, res: NextResponse) => {
     .getAll()
     .forEach(({ name, value }: { name: string; value: string }) => value && req.cookies.set(name, value));
 
-  const store = new StatelessSession(getConfig(config).baseConfig);
+  const store = new StatelessSession(getConfig(config));
   const [session] = await store.read(new Auth0NextRequest(req));
   return session;
 };
diff --git a/tests/helpers/testing.test.ts b/tests/helpers/testing.test.ts
index 95c7764d0..4d192d5a9 100644
--- a/tests/helpers/testing.test.ts
+++ b/tests/helpers/testing.test.ts
@@ -4,6 +4,7 @@ import { generateSessionCookie } from '../../src/helpers/testing';
 jest.mock('../../src/auth0-session/session/stateless-session');
 
 const encryptMock = jest.spyOn(CookieStore.prototype, 'encrypt');
+jest.spyOn(CookieStore.prototype, 'getKeys').mockReturnValue(Promise.resolve([]));
 const weekInSeconds = 7 * 24 * 60 * 60;
 
 describe('generate-session-cookie', () => {
@@ -54,7 +55,7 @@ describe('generate-session-cookie', () => {
 
   test('use the provided session', async () => {
     await generateSessionCookie({ user: { foo: 'bar' } }, { secret: '' });
-    expect(encryptMock).toHaveBeenCalledWith({ user: { foo: 'bar' } }, expect.anything());
+    expect(encryptMock).toHaveBeenCalledWith({ user: { foo: 'bar' } }, expect.anything(), undefined);
   });
 
   test('use the current time for the header values', async () => {
@@ -63,11 +64,15 @@ describe('generate-session-cookie', () => {
     const clock = jest.useFakeTimers();
     clock.setSystemTime(now);
     await generateSessionCookie({}, { secret: '' });
-    expect(encryptMock).toHaveBeenCalledWith(expect.anything(), {
-      iat: current,
-      uat: current,
-      exp: current + weekInSeconds
-    });
+    expect(encryptMock).toHaveBeenCalledWith(
+      expect.anything(),
+      {
+        iat: current,
+        uat: current,
+        exp: current + weekInSeconds
+      },
+      undefined
+    );
     clock.restoreAllMocks();
     jest.useRealTimers();
   });
diff --git a/tests/index.test.ts b/tests/index.test.ts
index 87cfcc679..084717642 100644
--- a/tests/index.test.ts
+++ b/tests/index.test.ts
@@ -1,7 +1,15 @@
 import { IncomingMessage, ServerResponse } from 'http';
 import { Socket } from 'net';
 import { withoutApi } from './fixtures/default-settings';
-import { WithApiAuthRequired, WithPageAuthRequired, InitAuth0, GetSession, ConfigParameters } from '../src';
+import {
+  WithApiAuthRequired,
+  WithPageAuthRequired,
+  InitAuth0,
+  GetSession,
+  ConfigParameters,
+  AppRouteHandlerFn
+} from '../src';
+import { NextRequest } from 'next/server';
 
 describe('index', () => {
   let withPageAuthRequired: WithPageAuthRequired,
@@ -31,9 +39,14 @@ describe('index', () => {
     jest.resetModules();
   });
 
-  test('withPageAuthRequired should not create an SDK instance at build time', () => {
+  test('withPageAuthRequired should not create an SDK instance at build time', async () => {
     process.env = { ...env, AUTH0_SECRET: undefined };
-    expect(() => withApiAuthRequired(jest.fn())).toThrow('"secret" is required');
+    await expect(() =>
+      withApiAuthRequired(jest.fn() as AppRouteHandlerFn)(new NextRequest(new URL('http://example.com')), {
+        params: {}
+      })
+    ).rejects.toThrow('"secret" is required');
+    expect(() => withApiAuthRequired(jest.fn())).not.toThrow();
     expect(() => withPageAuthRequired()).not.toThrow();
   });
 
diff --git a/tests/session/cache.test.ts b/tests/session/cache.test.ts
index cf4c3ad6c..370d10fcd 100644
--- a/tests/session/cache.test.ts
+++ b/tests/session/cache.test.ts
@@ -1,6 +1,7 @@
 import { IncomingMessage, ServerResponse } from 'http';
 import { Socket } from 'net';
-import { StatelessSession, getConfig } from '../../src/auth0-session';
+import { StatelessSession } from '../../src/auth0-session';
+import { getConfig } from '../../src/config';
 import { get, set } from '../../src/session/cache';
 import { ConfigParameters, Session, SessionCache } from '../../src';
 import { withoutApi } from '../fixtures/default-settings';
@@ -18,7 +19,8 @@ describe('SessionCache', () => {
     sessionStore.save = jest.fn();
     session = new Session({ sub: '__test_user__' });
     session.idToken = '__test_id_token__';
-    cache = new SessionCache(config, sessionStore);
+    cache = new SessionCache(() => config);
+    cache.getSessionStore = () => sessionStore;
     req = jest.mocked(new IncomingMessage(new Socket()));
     res = jest.mocked(new ServerResponse(req));
   };
diff --git a/tests/session/session.test.ts b/tests/session/session.test.ts
index 3c68c8498..a039776b4 100644
--- a/tests/session/session.test.ts
+++ b/tests/session/session.test.ts
@@ -2,6 +2,8 @@ import { TokenSet } from 'openid-client';
 import { fromJson, fromTokenEndpointResponse } from '../../src/session';
 import { makeIdToken } from '../auth0-session/fixtures/cert';
 import { Session } from '../../src';
+import { getConfig } from '../../src/config';
+import { withoutApi } from '../fixtures/default-settings';
 
 const routes = { login: '', callback: '', postLogoutRedirect: '' };
 
@@ -15,12 +17,15 @@ describe('session', () => {
   describe('from tokenSet', () => {
     test('should construct a session from a tokenSet', async () => {
       expect(
-        fromTokenEndpointResponse(new TokenSet({ id_token: await makeIdToken({ foo: 'bar', bax: 'qux' }) }), {
-          identityClaimFilter: ['baz'],
-          routes,
-          getLoginState,
-          session: { storeIDToken: true }
-        }).user
+        fromTokenEndpointResponse(
+          new TokenSet({ id_token: await makeIdToken({ foo: 'bar', bax: 'qux' }) }),
+          getConfig({
+            ...withoutApi,
+            identityClaimFilter: ['baz'],
+            getLoginState,
+            session: { storeIDToken: true }
+          })
+        ).user
       ).toEqual({
         aud: '__test_client_id__',
         bax: 'qux',
@@ -36,30 +41,38 @@ describe('session', () => {
 
     test('should store the ID Token by default', async () => {
       expect(
-        fromTokenEndpointResponse(new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }), {
-          identityClaimFilter: ['baz'],
-          routes,
-          getLoginState,
-          session: { storeIDToken: true }
-        }).idToken
+        fromTokenEndpointResponse(
+          new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }),
+          getConfig({
+            ...withoutApi,
+            identityClaimFilter: ['baz'],
+            routes,
+            getLoginState,
+            session: { storeIDToken: true }
+          })
+        ).idToken
       ).toBeDefined();
     });
 
     test('should not store the ID Token', async () => {
       expect(
-        fromTokenEndpointResponse(new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }), {
-          session: {
-            storeIDToken: false,
-            name: '',
-            rolling: false,
-            rollingDuration: 0,
-            absoluteDuration: 0,
-            cookie: { transient: false, httpOnly: false, sameSite: 'lax' }
-          },
-          getLoginState,
-          identityClaimFilter: ['baz'],
-          routes
-        }).idToken
+        fromTokenEndpointResponse(
+          new TokenSet({ id_token: await makeIdToken({ foo: 'bar' }) }),
+          getConfig({
+            ...withoutApi,
+            session: {
+              storeIDToken: false,
+              name: 'foo',
+              rolling: false,
+              rollingDuration: false,
+              absoluteDuration: 0,
+              cookie: { transient: false, httpOnly: false, sameSite: 'lax' }
+            },
+            getLoginState,
+            identityClaimFilter: ['baz'],
+            routes
+          })
+        ).idToken
       ).toBeUndefined();
     });
   });

From bec7d6c5998ba06ae87cb906235d7568bab2418f Mon Sep 17 00:00:00 2001
From: Adam Mcgrath <adamjmcgrath@gmail.com>
Date: Tue, 7 Nov 2023 13:06:04 +0000
Subject: [PATCH 3/7] Bail out of static rendering for pages and routes in app
 dir

---
 src/config.ts        | 10 +++++++++-
 tests/config.test.ts | 26 +++++++++++++++++++++++++-
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/src/config.ts b/src/config.ts
index 450d3d335..3251f45cd 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -263,8 +263,16 @@ export type GetConfig = (req: Auth0Request | Auth0RequestCookies) => Promise<Nex
 
 export const configSingletonGetter = (params: ConfigParameters = {}, genId: () => string): GetConfig => {
   let config: NextConfig;
-  return () => {
+  return (req) => {
     if (!config) {
+      // Bails out of static rendering for Server Components
+      // Need to query cookies because Server Components don't have access to URL
+      req.getCookies();
+      if ('getUrl' in req) {
+        // Bail out of static rendering for API Routes
+        // Reading cookies is not always enough https://github.com/vercel/next.js/issues/49006
+        req.getUrl();
+      }
       config = getConfig({ ...params, session: { genId, ...params.session } });
     }
     return config;
diff --git a/tests/config.test.ts b/tests/config.test.ts
index c6dd7bd7c..4afd999e6 100644
--- a/tests/config.test.ts
+++ b/tests/config.test.ts
@@ -1,4 +1,6 @@
-import { NextConfig, getConfig } from '../src/config';
+import { NextRequest } from 'next/server';
+import { NextConfig, getConfig, configSingletonGetter } from '../src/config';
+import { Auth0NextRequest, Auth0NextRequestCookies } from '../src/http';
 
 const getConfigWithEnv = (
   env: any = {},
@@ -259,4 +261,26 @@ describe('config params', () => {
       routes: expect.objectContaining({ callback: '/api/custom-callback' })
     });
   });
+
+  test('getConfig should query RSC cookies to bail out of static rendering', async () => {
+    const req = jest.mocked(new Auth0NextRequestCookies());
+    jest.spyOn(req, 'getCookies').mockImplementation(() => {
+      throw new Error('BAIL');
+    });
+    const getConfig = configSingletonGetter({}, () => '');
+    await expect(() => getConfig(req)).toThrow('BAIL');
+    await expect(() => getConfig(req)).not.toThrow('"secret" is required');
+    expect(req.getCookies).toHaveBeenCalled();
+  });
+
+  test('getConfig should query API route URL to bail out of static rendering', async () => {
+    const req = jest.mocked(new Auth0NextRequest(new NextRequest(new URL('http://example.com'))));
+    jest.spyOn(req, 'getUrl').mockImplementation(() => {
+      throw new Error('BAIL');
+    });
+    const getConfig = configSingletonGetter({}, () => '');
+    await expect(() => getConfig(req)).toThrow('BAIL');
+    await expect(() => getConfig(req)).not.toThrow('"secret" is required');
+    expect(req.getUrl).toHaveBeenCalled();
+  });
 });

From 77b2ef052f0c1ea46ed82c6c4dd33f197f19ba7c Mon Sep 17 00:00:00 2001
From: Adam Mcgrath <adamjmcgrath@gmail.com>
Date: Tue, 7 Nov 2023 13:30:45 +0000
Subject: [PATCH 4/7] Prepare experimental release

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 src/version.ts    | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 85a3c6194..bd9003957 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@auth0/nextjs-auth0",
-  "version": "3.2.0",
+  "version": "3.2.0-experimental-lazy-conf.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@auth0/nextjs-auth0",
-      "version": "3.2.0",
+      "version": "3.2.0-experimental-lazy-conf.0",
       "license": "MIT",
       "dependencies": {
         "@panva/hkdf": "^1.0.2",
diff --git a/package.json b/package.json
index de45cebaf..e5a2b2c4c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auth0/nextjs-auth0",
-  "version": "3.2.0",
+  "version": "3.2.0-experimental-lazy-conf.0",
   "description": "Next.js SDK for signing in with Auth0",
   "exports": {
     ".": "./dist/index.js",
diff --git a/src/version.ts b/src/version.ts
index 2e09da8c6..d1d7a0067 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export default '3.2.0';
+export default '3.2.0-experimental-lazy-conf.0';

From 62a26260bc541afaa222d160c46f25721049d5e1 Mon Sep 17 00:00:00 2001
From: Adam Mcgrath <adamjmcgrath@gmail.com>
Date: Tue, 7 Nov 2023 14:55:16 +0000
Subject: [PATCH 5/7] Fix edge client issue with reusing abort signal

---
 src/auth0-session/client/abstract-client.ts   |   3 +-
 src/auth0-session/client/edge-client.ts       | 150 +++++-----
 src/auth0-session/client/node-client.ts       | 261 ++++++++++--------
 .../auth0-session/client/edge-client.test.ts  |  26 +-
 .../auth0-session/client/node-client.test.ts  |  31 +--
 5 files changed, 252 insertions(+), 219 deletions(-)

diff --git a/src/auth0-session/client/abstract-client.ts b/src/auth0-session/client/abstract-client.ts
index 48f92edfd..54ca85f46 100644
--- a/src/auth0-session/client/abstract-client.ts
+++ b/src/auth0-session/client/abstract-client.ts
@@ -1,5 +1,5 @@
-import { Auth0Request } from '../http';
 import { Config } from '../config';
+import { Auth0Request } from '../http';
 
 export type Telemetry = {
   name: string;
@@ -85,6 +85,7 @@ export interface AuthorizationParameters {
 }
 
 export abstract class AbstractClient {
+  constructor(protected config: Config, protected telemetry: Telemetry) {}
   abstract authorizationUrl(parameters: Record<string, unknown>): Promise<string>;
   abstract callbackParams(req: Auth0Request, expectedState: string): Promise<URLSearchParams>;
   abstract callback(
diff --git a/src/auth0-session/client/edge-client.ts b/src/auth0-session/client/edge-client.ts
index 4d80ccb48..d8acd6a8f 100644
--- a/src/auth0-session/client/edge-client.ts
+++ b/src/auth0-session/client/edge-client.ts
@@ -26,17 +26,69 @@ const encodeBase64 = (input: string) => {
 };
 
 export class EdgeClient extends AbstractClient {
-  constructor(
-    private client: oauth.Client,
-    private as: oauth.AuthorizationServer,
-    private config: Config,
-    private httpOptions: oauth.HttpRequestOptions
-  ) {
-    super();
+  private client?: oauth.Client;
+  private as?: oauth.AuthorizationServer;
+  private httpOptions: () => oauth.HttpRequestOptions;
+
+  constructor(protected config: Config, protected telemetry: Telemetry) {
+    super(config, telemetry);
+    if (config.authorizationParams.response_type !== 'code') {
+      throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
+    }
+
+    this.httpOptions = () => {
+      const headers = new Headers();
+      if (config.enableTelemetry) {
+        const { name, version } = telemetry;
+        headers.set('User-Agent', `${name}/${version}`);
+        headers.set(
+          'Auth0-Client',
+          encodeBase64(
+            JSON.stringify({
+              name,
+              version,
+              env: {
+                edge: true
+              }
+            })
+          )
+        );
+      }
+      return {
+        signal: AbortSignal.timeout(this.config.httpTimeout),
+        headers
+      };
+    };
+  }
+
+  private async getClient(): Promise<[oauth.AuthorizationServer, oauth.Client]> {
+    if (this.as) {
+      return [this.as, this.client as oauth.Client];
+    }
+
+    const issuer = new URL(this.config.issuerBaseURL);
+    try {
+      this.as = await oauth
+        .discoveryRequest(issuer, this.httpOptions())
+        .then((response) => oauth.processDiscoveryResponse(issuer, response));
+    } catch (e) {
+      throw new DiscoveryError(e, this.config.issuerBaseURL);
+    }
+
+    this.client = {
+      client_id: this.config.clientID,
+      ...(!this.config.clientAssertionSigningKey && { client_secret: this.config.clientSecret }),
+      token_endpoint_auth_method: this.config.clientAuthMethod,
+      id_token_signed_response_alg: this.config.idTokenSigningAlg,
+      [oauth.clockTolerance]: this.config.clockTolerance
+    };
+
+    return [this.as, this.client];
   }
 
   async authorizationUrl(parameters: Record<string, unknown>): Promise<string> {
-    const authorizationUrl = new URL(this.as.authorization_endpoint as string);
+    const [as] = await this.getClient();
+    const authorizationUrl = new URL(as.authorization_endpoint as string);
     authorizationUrl.searchParams.set('client_id', this.config.clientID);
     Object.entries(parameters).forEach(([key, value]) => {
       if (value === null || value === undefined) {
@@ -48,11 +100,12 @@ export class EdgeClient extends AbstractClient {
   }
 
   async callbackParams(req: Auth0Request, expectedState: string) {
+    const [as, client] = await this.getClient();
     const url =
       req.getMethod().toUpperCase() === 'GET' ? new URL(req.getUrl()) : new URLSearchParams(await req.getBody());
     let result: ReturnType<typeof oauth.validateAuthResponse>;
     try {
-      result = oauth.validateAuthResponse(this.as, this.client, url, expectedState);
+      result = oauth.validateAuthResponse(as, client, url, expectedState);
     } catch (e) {
       throw new ApplicationError(e);
     }
@@ -72,6 +125,8 @@ export class EdgeClient extends AbstractClient {
     checks: OpenIDCallbackChecks,
     extras: CallbackExtras
   ): Promise<TokenEndpointResponse> {
+    const [as, client] = await this.getClient();
+
     const { clientAssertionSigningKey, clientAssertionSigningAlg } = this.config;
 
     let clientPrivateKey = clientAssertionSigningKey as CryptoKey | undefined;
@@ -80,21 +135,21 @@ export class EdgeClient extends AbstractClient {
       clientPrivateKey = await jose.importPKCS8<CryptoKey>(clientPrivateKey, clientAssertionSigningAlg || 'RS256');
     }
     const response = await oauth.authorizationCodeGrantRequest(
-      this.as,
-      this.client,
+      as,
+      client,
       parameters,
       redirectUri,
       checks.code_verifier as string,
       {
         additionalParameters: extras.exchangeBody,
         ...(clientPrivateKey && { clientPrivateKey }),
-        ...this.httpOptions
+        ...this.httpOptions()
       }
     );
 
     const result = await oauth.processAuthorizationCodeOpenIDResponse(
-      this.as,
-      this.client,
+      as,
+      client,
       response,
       checks.nonce,
       checks.max_age
@@ -110,14 +165,15 @@ export class EdgeClient extends AbstractClient {
   }
 
   async endSessionUrl(parameters: EndSessionParameters): Promise<string> {
-    const issuerUrl = new URL(this.as.issuer);
+    const [as] = await this.getClient();
+    const issuerUrl = new URL(as.issuer);
 
     if (
       this.config.idpLogout &&
       (this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
     ) {
       const { id_token_hint, post_logout_redirect_uri, ...extraParams } = parameters;
-      const auth0LogoutUrl: URL = new URL(urlJoin(this.as.issuer, '/v2/logout'));
+      const auth0LogoutUrl: URL = new URL(urlJoin(as.issuer, '/v2/logout'));
       post_logout_redirect_uri && auth0LogoutUrl.searchParams.set('returnTo', post_logout_redirect_uri);
       auth0LogoutUrl.searchParams.set('client_id', this.config.clientID);
       Object.entries(extraParams).forEach(([key, value]: [string, string]) => {
@@ -128,10 +184,10 @@ export class EdgeClient extends AbstractClient {
       });
       return auth0LogoutUrl.toString();
     }
-    if (!this.as.end_session_endpoint) {
+    if (!as.end_session_endpoint) {
       throw new Error('RP Initiated Logout is not supported on your Authorization Server.');
     }
-    const oidcLogoutUrl = new URL(this.as.end_session_endpoint);
+    const oidcLogoutUrl = new URL(as.end_session_endpoint);
     Object.entries(parameters).forEach(([key, value]: [string, string]) => {
       if (value === null || value === undefined) {
         return;
@@ -144,21 +200,23 @@ export class EdgeClient extends AbstractClient {
   }
 
   async userinfo(accessToken: string): Promise<Record<string, unknown>> {
-    const response = await oauth.userInfoRequest(this.as, this.client, accessToken, this.httpOptions);
+    const [as, client] = await this.getClient();
+    const response = await oauth.userInfoRequest(as, client, accessToken, this.httpOptions());
 
     try {
-      return await oauth.processUserInfoResponse(this.as, this.client, oauth.skipSubjectCheck, response);
+      return await oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response);
     } catch (e) {
       throw new UserInfoError(e.message);
     }
   }
 
   async refresh(refreshToken: string, extras: { exchangeBody: Record<string, any> }): Promise<TokenEndpointResponse> {
-    const res = await oauth.refreshTokenGrantRequest(this.as, this.client, refreshToken, {
+    const [as, client] = await this.getClient();
+    const res = await oauth.refreshTokenGrantRequest(as, client, refreshToken, {
       additionalParameters: extras.exchangeBody,
-      ...this.httpOptions
+      ...this.httpOptions()
     });
-    const result = await oauth.processRefreshTokenResponse(this.as, this.client, res);
+    const result = await oauth.processRefreshTokenResponse(as, client, res);
     if (oauth.isOAuth2Error(result)) {
       throw new AccessTokenError(
         AccessTokenErrorCode.FAILED_REFRESH_GRANT,
@@ -190,51 +248,7 @@ export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise
   let client: EdgeClient;
   return async (config) => {
     if (!client) {
-      const headers = new Headers();
-      if (config.enableTelemetry) {
-        const { name, version } = telemetry;
-        headers.set('User-Agent', `${name}/${version}`);
-        headers.set(
-          'Auth0-Client',
-          encodeBase64(
-            JSON.stringify({
-              name,
-              version,
-              env: {
-                edge: true
-              }
-            })
-          )
-        );
-      }
-      const httpOptions: oauth.HttpRequestOptions = {
-        signal: AbortSignal.timeout(config.httpTimeout),
-        headers
-      };
-
-      if (config.authorizationParams.response_type !== 'code') {
-        throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
-      }
-
-      const issuer = new URL(config.issuerBaseURL);
-      let as: oauth.AuthorizationServer;
-      try {
-        as = await oauth
-          .discoveryRequest(issuer, httpOptions)
-          .then((response) => oauth.processDiscoveryResponse(issuer, response));
-      } catch (e) {
-        throw new DiscoveryError(e, config.issuerBaseURL);
-      }
-
-      const oauthClient: oauth.Client = {
-        client_id: config.clientID,
-        ...(!config.clientAssertionSigningKey && { client_secret: config.clientSecret }),
-        token_endpoint_auth_method: config.clientAuthMethod,
-        id_token_signed_response_alg: config.idTokenSigningAlg,
-        [oauth.clockTolerance]: config.clockTolerance
-      };
-
-      client = new EdgeClient(oauthClient, as, config, httpOptions);
+      client = new EdgeClient(config, telemetry);
     }
     return client;
   };
diff --git a/src/auth0-session/client/node-client.ts b/src/auth0-session/client/node-client.ts
index da56a4b52..e4fa34c58 100644
--- a/src/auth0-session/client/node-client.ts
+++ b/src/auth0-session/client/node-client.ts
@@ -33,16 +33,142 @@ function sortSpaceDelimitedString(str: string): string {
 }
 
 export class NodeClient extends AbstractClient {
-  constructor(private client: Client) {
-    super();
+  private client?: Client;
+
+  private async getClient(): Promise<Client> {
+    if (this.client) {
+      return this.client;
+    }
+    const {
+      config,
+      telemetry: { name, version }
+    } = this;
+
+    const defaultHttpOptions: CustomHttpOptionsProvider = (_url, options) => ({
+      ...options,
+      headers: {
+        ...options.headers,
+        'User-Agent': `${name}/${version}`,
+        ...(config.enableTelemetry
+          ? {
+              'Auth0-Client': Buffer.from(
+                JSON.stringify({
+                  name,
+                  version,
+                  env: {
+                    node: process.version
+                  }
+                })
+              ).toString('base64')
+            }
+          : undefined)
+      },
+      timeout: config.httpTimeout,
+      agent: config.httpAgent
+    });
+    const applyHttpOptionsCustom = (entity: Issuer<Client> | typeof Issuer | Client) => {
+      entity[custom.http_options] = defaultHttpOptions;
+    };
+
+    applyHttpOptionsCustom(Issuer);
+    let issuer: Issuer<Client>;
+    try {
+      issuer = await Issuer.discover(config.issuerBaseURL);
+    } catch (e) {
+      throw new DiscoveryError(e, config.issuerBaseURL);
+    }
+    applyHttpOptionsCustom(issuer);
+
+    const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported)
+      ? issuer.id_token_signing_alg_values_supported
+      : [];
+    if (!issuerTokenAlgs.includes(config.idTokenSigningAlg)) {
+      debug(
+        'ID token algorithm %o is not supported by the issuer. Supported ID token algorithms are: %o.',
+        config.idTokenSigningAlg,
+        issuerTokenAlgs
+      );
+    }
+
+    const configRespType = sortSpaceDelimitedString(config.authorizationParams.response_type);
+    const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : [];
+    issuerRespTypes.map(sortSpaceDelimitedString);
+    if (!issuerRespTypes.includes(configRespType)) {
+      debug(
+        'Response type %o is not supported by the issuer. Supported response types are: %o.',
+        configRespType,
+        issuerRespTypes
+      );
+    }
+
+    const configRespMode = config.authorizationParams.response_mode;
+    const issuerRespModes = Array.isArray(issuer.response_modes_supported) ? issuer.response_modes_supported : [];
+    if (configRespMode && !issuerRespModes.includes(configRespMode)) {
+      debug(
+        'Response mode %o is not supported by the issuer. Supported response modes are %o.',
+        configRespMode,
+        issuerRespModes
+      );
+    }
+
+    let jwks;
+    if (config.clientAssertionSigningKey) {
+      const privateKey = createPrivateKey({ key: config.clientAssertionSigningKey as string });
+      const jwk = await exportJWK(privateKey);
+      jwks = { keys: [jwk] };
+    }
+
+    this.client = new issuer.Client(
+      {
+        client_id: config.clientID,
+        client_secret: config.clientSecret,
+        id_token_signed_response_alg: config.idTokenSigningAlg,
+        token_endpoint_auth_method: config.clientAuthMethod as ClientAuthMethod,
+        token_endpoint_auth_signing_alg: config.clientAssertionSigningAlg
+      },
+      jwks
+    );
+    applyHttpOptionsCustom(this.client);
+
+    this.client[custom.clock_tolerance] = config.clockTolerance;
+    const issuerUrl = new URL(issuer.metadata.issuer);
+
+    if (config.idpLogout) {
+      if (
+        this.config.idpLogout &&
+        (this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
+      ) {
+        Object.defineProperty(this.client, 'endSessionUrl', {
+          value(params: EndSessionParameters) {
+            const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;
+            const parsedUrl = new URL(urlJoin(issuer.metadata.issuer, '/v2/logout'));
+            parsedUrl.searchParams.set('client_id', config.clientID);
+            post_logout_redirect_uri && parsedUrl.searchParams.set('returnTo', post_logout_redirect_uri);
+            Object.entries(extraParams).forEach(([key, value]) => {
+              if (value === null || value === undefined) {
+                return;
+              }
+              parsedUrl.searchParams.set(key, value as string);
+            });
+            return parsedUrl.toString();
+          }
+        });
+      } else if (!issuer.end_session_endpoint) {
+        debug('the issuer does not support RP-Initiated Logout');
+      }
+    }
+
+    return this.client;
   }
 
   async authorizationUrl(parameters: Record<string, unknown>): Promise<string> {
-    return this.client.authorizationUrl(parameters);
+    const client = await this.getClient();
+    return client.authorizationUrl(parameters);
   }
 
   async callbackParams(req: Auth0Request) {
-    const obj: CallbackParamsType = this.client.callbackParams({
+    const client = await this.getClient();
+    const obj: CallbackParamsType = client.callbackParams({
       method: req.getMethod(),
       url: req.getUrl(),
       body: await req.getBody()
@@ -57,8 +183,9 @@ export class NodeClient extends AbstractClient {
     extras: CallbackExtras
   ): Promise<TokenEndpointResponse> {
     const params = Object.fromEntries(parameters.entries());
+    const client = await this.getClient();
     try {
-      return await this.client.callback(redirectUri, params, checks, extras);
+      return await client.callback(redirectUri, params, checks, extras);
     } catch (err) {
       if (err instanceof errors.OPError) {
         throw new IdentityProviderError(err);
@@ -72,20 +199,23 @@ export class NodeClient extends AbstractClient {
   }
 
   async endSessionUrl(parameters: EndSessionParameters): Promise<string> {
-    return this.client.endSessionUrl(parameters);
+    const client = await this.getClient();
+    return client.endSessionUrl(parameters);
   }
 
   async userinfo(accessToken: string): Promise<Record<string, unknown>> {
+    const client = await this.getClient();
     try {
-      return await this.client.userinfo(accessToken);
+      return await client.userinfo(accessToken);
     } catch (e) {
       throw new UserInfoError(e.message);
     }
   }
 
   async refresh(refreshToken: string, extras: { exchangeBody: Record<string, any> }): Promise<TokenEndpointResponse> {
+    const client = await this.getClient();
     try {
-      return await this.client.refresh(refreshToken, extras);
+      return await client.refresh(refreshToken, extras);
     } catch (e) {
       throw new AccessTokenError(
         AccessTokenErrorCode.FAILED_REFRESH_GRANT,
@@ -112,120 +242,7 @@ export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise
   let client: NodeClient;
   return async (config) => {
     if (!client) {
-      const { name, version } = telemetry;
-
-      const defaultHttpOptions: CustomHttpOptionsProvider = (_url, options) => ({
-        ...options,
-        headers: {
-          ...options.headers,
-          'User-Agent': `${name}/${version}`,
-          ...(config.enableTelemetry
-            ? {
-                'Auth0-Client': Buffer.from(
-                  JSON.stringify({
-                    name,
-                    version,
-                    env: {
-                      node: process.version
-                    }
-                  })
-                ).toString('base64')
-              }
-            : undefined)
-        },
-        timeout: config.httpTimeout,
-        agent: config.httpAgent
-      });
-      const applyHttpOptionsCustom = (entity: Issuer<Client> | typeof Issuer | Client) => {
-        entity[custom.http_options] = defaultHttpOptions;
-      };
-
-      applyHttpOptionsCustom(Issuer);
-      let issuer: Issuer<Client>;
-      try {
-        issuer = await Issuer.discover(config.issuerBaseURL);
-      } catch (e) {
-        throw new DiscoveryError(e, config.issuerBaseURL);
-      }
-      applyHttpOptionsCustom(issuer);
-
-      const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported)
-        ? issuer.id_token_signing_alg_values_supported
-        : [];
-      if (!issuerTokenAlgs.includes(config.idTokenSigningAlg)) {
-        debug(
-          'ID token algorithm %o is not supported by the issuer. Supported ID token algorithms are: %o.',
-          config.idTokenSigningAlg,
-          issuerTokenAlgs
-        );
-      }
-
-      const configRespType = sortSpaceDelimitedString(config.authorizationParams.response_type);
-      const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : [];
-      issuerRespTypes.map(sortSpaceDelimitedString);
-      if (!issuerRespTypes.includes(configRespType)) {
-        debug(
-          'Response type %o is not supported by the issuer. Supported response types are: %o.',
-          configRespType,
-          issuerRespTypes
-        );
-      }
-
-      const configRespMode = config.authorizationParams.response_mode;
-      const issuerRespModes = Array.isArray(issuer.response_modes_supported) ? issuer.response_modes_supported : [];
-      if (configRespMode && !issuerRespModes.includes(configRespMode)) {
-        debug(
-          'Response mode %o is not supported by the issuer. Supported response modes are %o.',
-          configRespMode,
-          issuerRespModes
-        );
-      }
-
-      let jwks;
-      if (config.clientAssertionSigningKey) {
-        const privateKey = createPrivateKey({ key: config.clientAssertionSigningKey as string });
-        const jwk = await exportJWK(privateKey);
-        jwks = { keys: [jwk] };
-      }
-
-      const oidcClient = new issuer.Client(
-        {
-          client_id: config.clientID,
-          client_secret: config.clientSecret,
-          id_token_signed_response_alg: config.idTokenSigningAlg,
-          token_endpoint_auth_method: config.clientAuthMethod as ClientAuthMethod,
-          token_endpoint_auth_signing_alg: config.clientAssertionSigningAlg
-        },
-        jwks
-      );
-      applyHttpOptionsCustom(oidcClient);
-
-      oidcClient[custom.clock_tolerance] = config.clockTolerance;
-      const issuerUrl = new URL(issuer.metadata.issuer);
-
-      if (config.idpLogout) {
-        if (config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false)) {
-          Object.defineProperty(oidcClient, 'endSessionUrl', {
-            value(params: EndSessionParameters) {
-              const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;
-              const parsedUrl = new URL(urlJoin(issuer.metadata.issuer, '/v2/logout'));
-              parsedUrl.searchParams.set('client_id', config.clientID);
-              post_logout_redirect_uri && parsedUrl.searchParams.set('returnTo', post_logout_redirect_uri);
-              Object.entries(extraParams).forEach(([key, value]) => {
-                if (value === null || value === undefined) {
-                  return;
-                }
-                parsedUrl.searchParams.set(key, value as string);
-              });
-              return parsedUrl.toString();
-            }
-          });
-        } else if (!issuer.end_session_endpoint) {
-          debug('the issuer does not support RP-Initiated Logout');
-        }
-      }
-
-      client = new NodeClient(oidcClient);
+      client = new NodeClient(config, telemetry);
     }
     return client;
   };
diff --git a/tests/auth0-session/client/edge-client.test.ts b/tests/auth0-session/client/edge-client.test.ts
index 5f37bc7a1..48b7805c9 100644
--- a/tests/auth0-session/client/edge-client.test.ts
+++ b/tests/auth0-session/client/edge-client.test.ts
@@ -8,7 +8,7 @@ import { jwks, makeIdToken } from '../fixtures/cert';
 import pkg from '../../../package.json';
 import wellKnown from '../fixtures/well-known.json';
 import version from '../../../src/version';
-import { EdgeClient, clientGetter } from '../../../src/auth0-session/client/edge-client';
+import { EdgeClient } from '../../../src/auth0-session/client/edge-client';
 import { mockFetch } from '../../fixtures/app-router-helpers';
 import { Auth0Request } from '../../../src/auth0-session/http';
 import { readFileSync } from 'fs';
@@ -50,14 +50,14 @@ const defaultConfig: ConfigParameters = {
 };
 
 const getClient = async (params: ConfigParameters = {}): Promise<EdgeClient> => {
-  return clientGetter({
+  return new EdgeClient(getConfig({ ...defaultConfig, ...params }), {
     name: 'nextjs-auth0',
     version
-  })(getConfig({ ...defaultConfig, ...params }));
+  });
 };
 
 describe('edge client', function () {
-  const headersSpy = jest.fn();
+  let headersSpy = jest.fn();
 
   beforeEach(() => {
     mockFetch();
@@ -147,7 +147,7 @@ describe('edge client', function () {
       idTokenSigningAlg: 'RS256'
     });
     // @ts-ignore
-    expect(client.client.id_token_signed_response_alg).toEqual('RS256');
+    expect((await client.getClient())[1].id_token_signed_response_alg).toEqual('RS256');
   });
 
   it('should use discovered logout endpoint by default', async function () {
@@ -264,10 +264,14 @@ describe('edge client', function () {
       );
 
     await expect(
-      getClient({
-        issuerBaseURL: 'https://op2.example.com',
-        idpLogout: true
-      })
+      (
+        await getClient({
+          issuerBaseURL: 'https://op2.example.com',
+          idpLogout: true
+        })
+      )
+        // @ts-ignore
+        .getClient()
     ).resolves.not.toThrow();
   });
 
@@ -275,7 +279,9 @@ describe('edge client', function () {
     nock.cleanAll();
     nock('https://op.example.com').get('/.well-known/oauth-authorization-server').reply(500);
     nock('https://op.example.com').get('/.well-known/openid-configuration').reply(500);
-    await expect(getClient()).rejects.toThrow(/Discovery requests failing for https:\/\/op.example.com/);
+    await expect((await getClient()).userinfo('token')).rejects.toThrow(
+      /Discovery requests failing for https:\/\/op.example.com/
+    );
   });
 
   it('should throw UserInfoError when userinfo fails', async () => {
diff --git a/tests/auth0-session/client/node-client.test.ts b/tests/auth0-session/client/node-client.test.ts
index 1ba87dbfb..1aa983d51 100644
--- a/tests/auth0-session/client/node-client.test.ts
+++ b/tests/auth0-session/client/node-client.test.ts
@@ -4,7 +4,7 @@ import { jwks } from '../fixtures/cert';
 import pkg from '../../../package.json';
 import wellKnown from '../fixtures/well-known.json';
 import version from '../../../src/version';
-import { NodeClient, clientGetter } from '../../../src/auth0-session/client/node-client';
+import { NodeClient } from '../../../src/auth0-session/client/node-client';
 import { UserInfoError } from '../../../src/auth0-session/utils/errors';
 
 const defaultConfig = {
@@ -19,10 +19,10 @@ const defaultConfig = {
 };
 
 const getClient = async (params: ConfigParameters = {}): Promise<NodeClient> => {
-  return clientGetter({
+  return new NodeClient(getConfig({ ...defaultConfig, ...params }), {
     name: 'nextjs-auth0',
     version
-  })(getConfig({ ...defaultConfig, ...params }));
+  });
 };
 
 describe('node client', function () {
@@ -69,15 +69,6 @@ describe('node client', function () {
     expect(headerProps).not.toContain('auth0-client');
   });
 
-  it('should accept lazy config', async function () {
-    expect(() =>
-      clientGetter({
-        name: 'nextjs-auth0',
-        version
-      })
-    ).not.toThrow();
-  });
-
   it('should not strip new headers', async function () {
     const client = await getClient();
     const response = await client.userinfo('__test_token__');
@@ -101,7 +92,7 @@ describe('node client', function () {
       idTokenSigningAlg: 'RS256'
     });
     // @ts-ignore
-    expect((await client.client).id_token_signed_response_alg).toEqual('RS256');
+    expect((await client.getClient()).id_token_signed_response_alg).toEqual('RS256');
   });
 
   it('should use discovered logout endpoint by default', async function () {
@@ -203,10 +194,14 @@ describe('node client', function () {
       );
 
     await expect(
-      getClient({
-        issuerBaseURL: 'https://op2.example.com',
-        idpLogout: true
-      })
+      (
+        await getClient({
+          issuerBaseURL: 'https://op2.example.com',
+          idpLogout: true
+        })
+      )
+        // @ts-ignore
+        .getClient()
     ).resolves.not.toThrow();
   });
 
@@ -214,7 +209,7 @@ describe('node client', function () {
     nock.cleanAll();
     nock('https://op.example.com').get('/.well-known/oauth-authorization-server').reply(500);
     nock('https://op.example.com').get('/.well-known/openid-configuration').reply(500);
-    await expect(getClient).rejects.toThrow(
+    await expect((await getClient()).userinfo('token')).rejects.toThrow(
       'Discovery requests failing for https://op.example.com, expected 200 OK, got: 500 Internal Server Error'
     );
   });

From 9d91b5897b1b5ad46736cf744bf483caa689d4f0 Mon Sep 17 00:00:00 2001
From: Adam Mcgrath <adamjmcgrath@gmail.com>
Date: Mon, 13 Nov 2023 09:23:01 +0000
Subject: [PATCH 6/7] revert experimental version

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 src/version.ts    | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 4f8bcc1e9..3e1d6df60 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@auth0/nextjs-auth0",
-  "version": "3.2.0-experimental-lazy-conf.0",
+  "version": "3.2.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@auth0/nextjs-auth0",
-      "version": "3.2.0-experimental-lazy-conf.0",
+      "version": "3.2.0",
       "license": "MIT",
       "dependencies": {
         "@panva/hkdf": "^1.0.2",
diff --git a/package.json b/package.json
index 4384a04fb..508057c41 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auth0/nextjs-auth0",
-  "version": "3.2.0-experimental-lazy-conf.0",
+  "version": "3.2.0",
   "description": "Next.js SDK for signing in with Auth0",
   "exports": {
     ".": "./dist/index.js",
diff --git a/src/version.ts b/src/version.ts
index d1d7a0067..2e09da8c6 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export default '3.2.0-experimental-lazy-conf.0';
+export default '3.2.0';

From ed41013cd71085500f83cbc59c6a8149193219b5 Mon Sep 17 00:00:00 2001
From: Adam Mcgrath <adamjmcgrath@gmail.com>
Date: Mon, 13 Nov 2023 13:02:43 +0000
Subject: [PATCH 7/7] Remove old TODO

---
 src/session/get-access-token.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/session/get-access-token.ts b/src/session/get-access-token.ts
index c4834b4f9..ac9a88454 100644
--- a/src/session/get-access-token.ts
+++ b/src/session/get-access-token.ts
@@ -236,7 +236,6 @@ export default function accessTokenFactory(
   return async (reqOrOpts?, res?, accessTokenRequest?): Promise<GetAccessTokenResult> => {
     const options = (res ? accessTokenRequest : reqOrOpts) as AccessTokenRequest | undefined;
     const req = (res ? reqOrOpts : undefined) as IncomingMessage | NextApiRequest | undefined;
-    // TODO: clean up
     const config = await getConfig(req ? getAuth0ReqRes(req, res as any)[0] : new Auth0NextRequestCookies());
     const client = await getClient(config);