Repository: auth0/nextjs-auth0
Pull request #825 (2022-09-20 13:59:45)

[SDK-3556] Prevent mixing named exports and own instances
- [ ] All new/changed/fixed functionality is covered by tests (or N/A)
- [ ] I have added documentation for all new/changed functionality (or N/A)

### 📋 Changes

Mixing named exports and a self created instance can lead to confusing errors, when this happens we will now throw an error to inform the developer.

```js
import { initAuth0, getSession } from '@auth0/nextjs-auth0';

const instance = initAuth0();

export default instance.withApiAuthRequired(req, res) {
  const session = await getSession(req, res);
  // 💥 Errors because they should be using `instance.getSession`
}
```

### 📎 References

See #429 #526 #725 


Patch:
diff --git a/package.json b/package.json
index 0cbdefa6..26f5a172 100644
--- a/package.json
+++ b/package.json
@@ -136,7 +136,6 @@
     "collectCoverageFrom": [
       "<rootDir>/src/**/*.*",
       "!<rootDir>/src/index.browser.ts",
-      "!<rootDir>/src/index.ts",
       "!<rootDir>/src/middleware.ts",
       "!<rootDir>/src/instance.ts",
       "!<rootDir>/src/handlers/auth.ts",
diff --git a/src/index.ts b/src/index.ts
index 1498df31..03548181 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -52,10 +52,13 @@ import {
 import { InitAuth0, SignInWithAuth0 } from './instance';
 import version from './version';
 import { getConfig, getLoginUrl, ConfigParameters } from './config';
+import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check';
 
 let instance: SignInWithAuth0 & { sessionCache: SessionCache };
 
+// For using managed instance with named exports.
 function getInstance(): SignInWithAuth0 & { sessionCache: SessionCache } {
+  setIsUsingNamedExports();
   if (instance) {
     return instance;
   }
@@ -63,6 +66,13 @@ function getInstance(): SignInWithAuth0 & { sessionCache: SessionCache } {
   return instance;
 }
 
+// For creating own instance.
+export const initAuth0: InitAuth0 = (params) => {
+  setIsUsingOwnInstance();
+  const { sessionCache, ...publicApi } = _initAuth(params); // eslint-disable-line @typescript-eslint/no-unused-vars
+  return publicApi;
+};
+
 export const _initAuth = (params?: ConfigParameters): SignInWithAuth0 & { sessionCache: SessionCache } => {
   const { baseConfig, nextConfig } = getConfig(params);
 
@@ -102,11 +112,7 @@ export const _initAuth = (params?: ConfigParameters): SignInWithAuth0 & { sessio
   };
 };
 
-export const initAuth0: InitAuth0 = (params) => {
-  const { sessionCache, ...publicApi } = _initAuth(params); // eslint-disable-line @typescript-eslint/no-unused-vars
-  return publicApi;
-};
-
+/* c8 ignore start */
 const getSessionCache = () => getInstance().sessionCache;
 export const getSession: GetSession = (...args) => getInstance().getSession(...args);
 export const updateUser: UpdateUser = (...args) => getInstance().updateUser(...args);
@@ -156,6 +162,7 @@ export {
   WithPageAuthRequired,
   SessionCache,
   GetSession,
+  UpdateUser,
   GetAccessToken,
   Session,
   Claims,
@@ -166,5 +173,7 @@ export {
   AfterRefetch,
   LoginOptions,
   LogoutOptions,
-  GetLoginState
+  GetLoginState,
+  InitAuth0
 };
+/* c8 ignore stop */
diff --git a/src/middleware.ts b/src/middleware.ts
index 21ea28c7..7ddd0c47 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -7,6 +7,7 @@ import {
   default as withMiddlewareAuthRequiredFactory
 } from './helpers/with-middleware-auth-required';
 import { getConfig, ConfigParameters } from './config';
+import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check';
 
 export type Instance = { withMiddlewareAuthRequired: WithMiddlewareAuthRequired; getSession: GetSession };
 
@@ -19,14 +20,20 @@ export { WithMiddlewareAuthRequired };
 let instance: Instance;
 
 function getInstance(params?: ConfigParameters): Instance {
+  setIsUsingNamedExports();
   if (instance) {
     return instance;
   }
-  instance = initAuth(params);
+  instance = _initAuth0(params);
   return instance;
 }
 
-export const initAuth: InitAuth0 = (params?) => {
+export const initAuth0: InitAuth0 = (params?) => {
+  setIsUsingOwnInstance();
+  return _initAuth0(params);
+};
+
+const _initAuth0: InitAuth0 = (params?) => {
   const { baseConfig, nextConfig } = getConfig(params);
 
   // Init base layer (with base config)
diff --git a/src/utils/instance-check.ts b/src/utils/instance-check.ts
new file mode 100644
index 00000000..4786da05
--- /dev/null
+++ b/src/utils/instance-check.ts
@@ -0,0 +1,21 @@
+let isUsingNamedExports = false;
+let isUsingOwnInstance = false;
+
+const instanceCheck = () => {
+  if (isUsingNamedExports && isUsingOwnInstance) {
+    throw new Error(
+      'You cannot mix creating your own instance with `initAuth0` and using named ' +
+        "exports like `import { handleAuth } from '@auth0/nextjs-auth0'`"
+    );
+  }
+};
+
+export const setIsUsingNamedExports = (): void => {
+  isUsingNamedExports = true;
+  instanceCheck();
+};
+
+export const setIsUsingOwnInstance = (): void => {
+  isUsingOwnInstance = true;
+  instanceCheck();
+};


Test patch:
diff --git a/tests/helpers/with-middleware-auth-required.test.ts b/tests/helpers/with-middleware-auth-required.test.ts
index f38852ca..a342bee3 100644
--- a/tests/helpers/with-middleware-auth-required.test.ts
+++ b/tests/helpers/with-middleware-auth-required.test.ts
@@ -3,7 +3,7 @@
  */
 import { NextRequest, NextResponse } from 'next/server';
 import { NextFetchEvent } from 'next/dist/server/web/spec-extension/fetch-event';
-import { initAuth } from '../../src/middleware';
+import { initAuth0 } from '../../src/middleware';
 import { withoutApi } from '../fixtures/default-settings';
 import { IdTokenClaims } from 'openid-client';
 import { encryption as deriveKey } from '../../src/auth0-session/utils/hkdf';
@@ -35,7 +35,7 @@ const encrypted = async (claims: Partial<IdTokenClaims> = { sub: '__test_sub__'
 };
 
 const setup = async ({ url = 'http://example.com', config = withoutApi, user, middleware }: any = {}) => {
-  const mw = initAuth(config).withMiddlewareAuthRequired(middleware);
+  const mw = initAuth0(config).withMiddlewareAuthRequired(middleware);
   const request = new NextRequest(new URL(url));
   if (user) {
     request.cookies.set('appSession', await encrypted({ sub: 'foo' }));
diff --git a/tests/index.test.ts b/tests/index.test.ts
index e8b8026c..9d2adfcb 100644
--- a/tests/index.test.ts
+++ b/tests/index.test.ts
@@ -1,11 +1,67 @@
-import { withPageAuthRequired, withApiAuthRequired } from '../src';
+import { IncomingMessage, ServerResponse } from 'http';
+import { Socket } from 'net';
+import { withoutApi } from './fixtures/default-settings';
+import { WithApiAuthRequired, WithPageAuthRequired, InitAuth0, GetSession, ConfigParameters } from '../src';
 
 describe('index', () => {
+  let withPageAuthRequired: WithPageAuthRequired,
+    withApiAuthRequired: WithApiAuthRequired,
+    initAuth0: InitAuth0,
+    getSession: GetSession;
+  let env: NodeJS.ProcessEnv;
+
+  const updateEnv = (opts: ConfigParameters) => {
+    process.env = {
+      ...env,
+      AUTH0_ISSUER_BASE_URL: opts.issuerBaseURL,
+      AUTH0_CLIENT_ID: opts.clientID,
+      AUTH0_CLIENT_SECRET: opts.clientSecret,
+      AUTH0_BASE_URL: opts.baseURL,
+      AUTH0_SECRET: opts.secret as string
+    };
+  };
+
+  beforeEach(async () => {
+    env = process.env;
+    ({ withPageAuthRequired, withApiAuthRequired, initAuth0, getSession } = await import('../src'));
+  });
+
+  afterEach(() => {
+    process.env = env;
+    jest.resetModules();
+  });
+
   test('withPageAuthRequired should not create an SDK instance at build time', () => {
-    const secret = process.env.AUTH0_SECRET;
-    delete process.env.AUTH0_SECRET;
+    process.env = { ...env, AUTH0_SECRET: undefined };
     expect(() => withApiAuthRequired(jest.fn())).toThrow('"secret" is required');
     expect(() => withPageAuthRequired()).not.toThrow();
-    process.env.AUTH0_SECRET = secret;
+  });
+
+  test('should error when mixing named exports and own instance', async () => {
+    const instance = initAuth0(withoutApi);
+    const req = new IncomingMessage(new Socket());
+    const res = new ServerResponse(req);
+    await expect(instance.getSession(req, res)).resolves.toBeNull();
+    expect(() => getSession(req, res)).toThrow(
+      "You cannot mix creating your own instance with `initAuth0` and using named exports like `import { handleAuth } from '@auth0/nextjs-auth0'`"
+    );
+  });
+
+  test('should error when mixing own instance and named exports', async () => {
+    updateEnv(withoutApi);
+    const req = new IncomingMessage(new Socket());
+    const res = new ServerResponse(req);
+    await expect(getSession(req, res)).resolves.toBeNull();
+    expect(() => initAuth0()).toThrow(
+      "You cannot mix creating your own instance with `initAuth0` and using named exports like `import { handleAuth } from '@auth0/nextjs-auth0'`"
+    );
+  });
+
+  test('should share insance when using named exports', async () => {
+    updateEnv(withoutApi);
+    const req = new IncomingMessage(new Socket());
+    const res = new ServerResponse(req);
+    await expect(getSession(req, res)).resolves.toBeNull();
+    await expect(getSession(req, res)).resolves.toBeNull();
   });
 });