diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts
index 13dad47aa48..8ef8d7e9651 100644
--- a/integration/presets/envs.ts
+++ b/integration/presets/envs.ts
@@ -29,8 +29,7 @@ const withEmailCodes = environmentConfig()
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk)
.setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in')
.setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-up')
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withEmailLinks = environmentConfig()
.setId('withEmailLinks')
@@ -39,8 +38,7 @@ const withEmailLinks = environmentConfig()
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-links'].pk)
.setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in')
.setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-up')
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withCustomRoles = environmentConfig()
.setId('withCustomRoles')
@@ -51,8 +49,7 @@ const withCustomRoles = environmentConfig()
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-custom-roles'].pk)
.setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in')
.setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-up')
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withEmailCodesQuickstart = withEmailCodes
.removeEnvVariable('public', 'CLERK_SIGN_IN_URL')
@@ -63,30 +60,33 @@ const withAPCore1ClerkLatest = environmentConfig()
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['with-email-codes'].sk)
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk)
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withAPCore1ClerkV4 = environmentConfig()
.setId('withAPCore1ClerkV4')
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['with-email-codes'].sk)
- .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk)
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk);
const withAPCore2ClerkLatest = environmentConfig()
.setId('withAPCore2ClerkLatest')
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['core-2-all-enabled'].sk)
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['core-2-all-enabled'].pk)
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withAPCore2ClerkV4 = environmentConfig()
.setId('withAPCore2ClerkV4')
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['core-2-all-enabled'].sk)
- .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['core-2-all-enabled'].pk)
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['core-2-all-enabled'].pk);
+
+const withDynamicKeys = withEmailCodes
+ .clone()
+ .setId('withDynamicKeys')
+ .setEnvVariable('private', 'CLERK_SECRET_KEY', '')
+ .setEnvVariable('private', 'CLERK_DYNAMIC_SECRET_KEY', envKeys['with-email-codes'].sk)
+ .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY);
export const envs = {
withEmailCodes,
@@ -97,4 +97,5 @@ export const envs = {
withAPCore1ClerkV4,
withAPCore2ClerkLatest,
withAPCore2ClerkV4,
+ withDynamicKeys,
} as const;
diff --git a/integration/tests/dynamic-keys.test.ts b/integration/tests/dynamic-keys.test.ts
new file mode 100644
index 00000000000..4c08e8656ce
--- /dev/null
+++ b/integration/tests/dynamic-keys.test.ts
@@ -0,0 +1,101 @@
+import { expect, test } from '@playwright/test';
+
+import type { Application } from '../models/application';
+import { appConfigs } from '../presets';
+import { createTestUtils } from '../testUtils';
+
+test.describe('dynamic keys @nextjs', () => {
+ test.describe.configure({ mode: 'parallel' });
+ let app: Application;
+
+ test.beforeAll(async () => {
+ app = await appConfigs.next.appRouter
+ .clone()
+ .addFile(
+ 'src/middleware.ts',
+ () => `import { clerkClient, clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
+ import { NextResponse } from 'next/server'
+
+ const isProtectedRoute = createRouteMatcher(['/protected']);
+ const shouldFetchBapi = createRouteMatcher(['/fetch-bapi-from-middleware']);
+
+ export default clerkMiddleware(async (auth, request) => {
+ if (isProtectedRoute(request)) {
+ auth().protect();
+ }
+
+ if (shouldFetchBapi(request)){
+ const count = await clerkClient().users.getCount();
+
+ if (count){
+ return NextResponse.redirect(new URL('/users-count', request.url))
+ }
+ }
+ }, {
+ secretKey: process.env.CLERK_DYNAMIC_SECRET_KEY,
+ signInUrl: '/foobar'
+ });
+
+ export const config = {
+ matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
+ };`,
+ )
+ .addFile(
+ 'src/app/users-count/page.tsx',
+ () => `import { clerkClient } from '@clerk/nextjs/server'
+
+ export default async function Page(){
+ const count = await clerkClient().users.getCount()
+
+ return
Users count: {count}
+ }
+ `,
+ )
+ .commit();
+
+ await app.setup();
+ await app.withEnv(appConfigs.envs.withDynamicKeys);
+ await app.dev();
+ });
+
+ test.afterAll(async () => {
+ await app.teardown();
+ });
+
+ test.afterEach(async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.signOut();
+ await u.page.context().clearCookies();
+ });
+
+ test('redirects to `signInUrl` on `auth().protect()`', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ await u.page.goToStart();
+
+ await u.po.expect.toBeSignedOut();
+
+ await u.page.goToRelative('/protected');
+
+ await u.page.waitForURL(/foobar/);
+ });
+
+ test('resolves auth signature with `secretKey` on `auth().protect()`', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/page-protected');
+ await u.page.waitForURL(/foobar/);
+ });
+
+ test('calls `clerkClient` with dynamic keys from application runtime', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/users-count');
+ await expect(u.page.getByText(/Users count/i)).toBeVisible();
+ });
+
+ test('calls `clerkClient` with dynamic keys from middleware runtime', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/fetch-bapi-from-middleware');
+ await u.page.waitForAppUrl('/users-count');
+ await expect(u.page.getByText(/Users count/i)).toBeVisible();
+ });
+});
diff --git a/integration/tests/handshake.test.ts b/integration/tests/handshake.test.ts
index 5057145ef21..7d8cd203e08 100644
--- a/integration/tests/handshake.test.ts
+++ b/integration/tests/handshake.test.ts
@@ -8,7 +8,7 @@ import { generateConfig, getJwksFromSecretKey } from '../testUtils/handshake';
const PORT = 4199;
-test.skip('Client handshake @generic', () => {
+test.describe('Client handshake @generic', () => {
test.describe.configure({ mode: 'serial' });
let app: Application;
diff --git a/packages/nextjs/src/app-router/server/currentUser.ts b/packages/nextjs/src/app-router/server/currentUser.ts
index ae3a2090f85..4d7cb2d7be1 100644
--- a/packages/nextjs/src/app-router/server/currentUser.ts
+++ b/packages/nextjs/src/app-router/server/currentUser.ts
@@ -9,5 +9,5 @@ export async function currentUser(): Promise {
return null;
}
- return clerkClient.users.getUser(userId);
+ return clerkClient().users.getUser(userId);
}
diff --git a/packages/nextjs/src/server/__tests__/clerkClient.test.ts b/packages/nextjs/src/server/__tests__/clerkClient.test.ts
index 1ceedefc0f7..58b6e883ddd 100644
--- a/packages/nextjs/src/server/__tests__/clerkClient.test.ts
+++ b/packages/nextjs/src/server/__tests__/clerkClient.test.ts
@@ -4,7 +4,7 @@ import { clerkClient } from '../clerkClient';
describe('clerkClient', () => {
it('should pass version package to userAgent', async () => {
- await clerkClient.users.getUser('user_test');
+ await clerkClient().users.getUser('user_test');
expect(global.fetch).toBeCalled();
expect((global.fetch as any).mock.calls[0][1].headers).toMatchObject({
diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
index d4e19aa1609..2763d06f9ba 100644
--- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
+++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
@@ -253,7 +253,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to sign-in url when redirectToSignIn is called with the correct returnBackUrl', async () => {
@@ -270,7 +270,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toContain('/protected');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to sign-in url with redirect_url set to the provided returnBackUrl param', async () => {
@@ -289,7 +289,7 @@ describe('clerkMiddleware(params)', () => {
expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toEqual(
'https://www.clerk.com/hello',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to sign-in url without a redirect_url when returnBackUrl is null', async () => {
@@ -306,7 +306,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toBeNull();
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
@@ -331,7 +331,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('does not redirect to sign-in url when protect is called, the user is signed in and the request is a page request', async () => {
@@ -354,7 +354,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get('location')).toBeFalsy();
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('throws a not found error when protect is called, the user is signed out, and is not a page request', async () => {
@@ -377,7 +377,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get(constants.Headers.AuthReason)).toContain('protect-rewrite');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('throws a not found error when protect is called with RBAC params the user does not fulfill, and is a page request', async () => {
@@ -400,7 +400,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get(constants.Headers.AuthReason)).toContain('protect-rewrite');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthenticatedUrl when protect is called with the redirectUrl param, the user is signed out, and is a page request', async () => {
@@ -424,7 +424,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://www.clerk.com/hello');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthorizedUrl when protect is called with the redirectUrl param, the user does not fulfill the RBAC params, and is a page request', async () => {
@@ -454,7 +454,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://www.clerk.com/discover');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
@@ -479,7 +479,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('forwards headers from authenticateRequest when auth().protect() is called', async () => {
@@ -507,7 +507,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.headers.get('X-Clerk-Auth')).toEqual('1');
expect(resp?.headers.get('Set-Cookie')).toEqual('session=;');
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthenticatedUrl when protect is called with the unauthenticatedUrl param, the user is signed out, and is a page request', async () => {
@@ -534,7 +534,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('https://www.clerk.com/unauthenticatedUrl');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthorizedUrl when protect is called with the unauthorizedUrl param, the user is signed in but does not have permissions, and is a page request', async () => {
@@ -564,7 +564,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('https://www.clerk.com/unauthorizedUrl');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
});
@@ -592,7 +592,7 @@ describe('Dev Browser JWT when redirecting to cross origin for page requests', f
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('appends the Dev Browser JWT to the search when cookie __clerk_db_jwt exists and location is an Account Portal URL', async () => {
@@ -616,7 +616,7 @@ describe('Dev Browser JWT when redirecting to cross origin for page requests', f
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected&__clerk_db_jwt=test_jwt',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('does NOT append the Dev Browser JWT if x-clerk-redirect-to header is not set (user-returned redirect)', async () => {
@@ -642,6 +642,6 @@ describe('Dev Browser JWT when redirecting to cross origin for page requests', f
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
diff --git a/packages/nextjs/src/server/clerkClient.ts b/packages/nextjs/src/server/clerkClient.ts
index 41b6559b5a3..eb8006408ec 100644
--- a/packages/nextjs/src/server/clerkClient.ts
+++ b/packages/nextjs/src/server/clerkClient.ts
@@ -1,5 +1,10 @@
+import type { ClerkClient } from '@clerk/backend';
import { createClerkClient } from '@clerk/backend';
+import type { AuthenticateRequestOptions } from '@clerk/backend/internal';
+import { constants } from '@clerk/backend/internal';
+import { deprecated } from '@clerk/shared/deprecated';
+import { buildRequestLike } from '../app-router/server/utils';
import {
API_URL,
API_VERSION,
@@ -12,8 +17,9 @@ import {
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
+import { decryptClerkRequestData, getHeader } from './utils';
-const clerkClient = createClerkClient({
+const clerkClientDefaultOptions = {
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
@@ -27,6 +33,65 @@ const clerkClient = createClerkClient({
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
},
+};
+
+export const clerkClientStorage = new AsyncLocalStorage>();
+
+const createClerkClientWithOptions: typeof createClerkClient = options =>
+ createClerkClient({ ...clerkClientDefaultOptions, ...options });
+
+const clerkClientSingleton = createClerkClient(clerkClientDefaultOptions);
+
+/**
+ * @deprecated
+ * This singleton is deprecated and will be removed in a future release. Please use `clerkClient()` as a function instead.
+ */
+const clerkClientSingletonProxy = new Proxy(clerkClientSingleton, {
+ get(target, prop, receiver) {
+ if (Object.getPrototypeOf(target) === Object.getPrototypeOf(clerkClientSingleton)) {
+ deprecated('clerkClient object', 'Use `clerkClient()` as a function instead.');
+ }
+
+ return Reflect.get(target, prop, receiver);
+ },
});
+/**
+ * Constructs a BAPI client that accesses request data within the runtime.
+ * Necessary if middleware dynamic keys are used.
+ */
+const clerkClientForRequest = () => {
+ let requestData: Partial | undefined;
+
+ /**
+ * For BAPI client usage inside middleware runtime, fallbacks to AsyncLocalStorage to access request data
+ */
+ const clerkClientStore = clerkClientStorage.getStore();
+ if (clerkClientStore) {
+ requestData = clerkClientStore;
+ } else {
+ /**
+ * For BAPI usage from application server, fallbacks to access request data via `NextRequest`
+ */
+ const request = buildRequestLike();
+ const encryptedRequestData = getHeader(request, constants.Headers.ClerkRequestData);
+ requestData = decryptClerkRequestData(encryptedRequestData);
+ }
+
+ if (requestData?.secretKey || requestData?.publishableKey) {
+ return createClerkClientWithOptions({
+ secretKey: requestData.secretKey,
+ publishableKey: requestData.publishableKey,
+ });
+ }
+
+ return clerkClientSingleton;
+};
+
+const clerkClient: ClerkClient & typeof clerkClientForRequest = Object.assign(
+ clerkClientForRequest,
+ // TODO SDK-1839 - Remove `clerkClient` singleton in the next major version of `@clerk/nextjs`
+ clerkClientSingletonProxy,
+);
+
export { clerkClient };
diff --git a/packages/nextjs/src/server/clerkMiddleware.ts b/packages/nextjs/src/server/clerkMiddleware.ts
index 8d4a00dcad8..6de46fe0283 100644
--- a/packages/nextjs/src/server/clerkMiddleware.ts
+++ b/packages/nextjs/src/server/clerkMiddleware.ts
@@ -12,7 +12,7 @@ import { NextResponse } from 'next/server';
import { isRedirect, serverRedirectWithAuth, setHeader } from '../utils';
import { withLogger } from '../utils/debugLogger';
-import { clerkClient } from './clerkClient';
+import { clerkClient, clerkClientStorage } from './clerkClient';
import { PUBLISHABLE_KEY, SECRET_KEY, SIGN_IN_URL, SIGN_UP_URL } from './constants';
import { errorThrower } from './errorThrower';
import type { AuthProtect } from './protect';
@@ -91,82 +91,88 @@ export const clerkMiddleware: ClerkMiddleware = withLogger('clerkMiddleware', lo
signUpUrl,
};
- clerkClient.telemetry.record(
- eventMethodCalled('clerkMiddleware', {
- handler: Boolean(handler),
- satellite: Boolean(options.isSatellite),
- proxy: Boolean(options.proxyUrl),
- }),
- );
-
- const nextMiddleware: NextMiddleware = async (request, event) => {
- const clerkRequest = createClerkRequest(request);
- logger.debug('options', options);
- logger.debug('url', () => clerkRequest.toJSON());
-
- const requestState = await clerkClient.authenticateRequest(
- clerkRequest,
- createAuthenticateRequestOptions(clerkRequest, options),
+ return clerkClientStorage.run(options, () => {
+ clerkClient().telemetry.record(
+ eventMethodCalled('clerkMiddleware', {
+ handler: Boolean(handler),
+ satellite: Boolean(options.isSatellite),
+ proxy: Boolean(options.proxyUrl),
+ }),
);
- logger.debug('requestState', () => ({
- status: requestState.status,
- headers: JSON.stringify(Object.fromEntries(requestState.headers)),
- reason: requestState.reason,
- }));
-
- const locationHeader = requestState.headers.get(constants.Headers.Location);
- if (locationHeader) {
- return new Response(null, { status: 307, headers: requestState.headers });
- } else if (requestState.status === AuthStatus.Handshake) {
- throw new Error('Clerk: handshake status without redirect');
- }
-
- const authObject = requestState.toAuth();
- logger.debug('auth', () => ({ auth: authObject, debug: authObject.debug() }));
+ const nextMiddleware: NextMiddleware = async (request, event) => {
+ const clerkRequest = createClerkRequest(request);
+ logger.debug('options', options);
+ logger.debug('url', () => clerkRequest.toJSON());
- const redirectToSignIn = createMiddlewareRedirectToSignIn(clerkRequest);
- const protect = createMiddlewareProtect(clerkRequest, authObject, redirectToSignIn);
- const authObjWithMethods: ClerkMiddlewareAuthObject = Object.assign(authObject, { protect, redirectToSignIn });
-
- let handlerResult: Response = NextResponse.next();
- try {
- handlerResult = (await handler?.(() => authObjWithMethods, request, event)) || handlerResult;
- } catch (e: any) {
- handlerResult = handleControlFlowErrors(e, clerkRequest, requestState);
- }
-
- // TODO @nikos: we need to make this more generic
- // and move the logic in clerk/backend
- if (requestState.headers) {
- requestState.headers.forEach((value, key) => {
- handlerResult.headers.append(key, value);
- });
- }
+ const requestState = await clerkClient().authenticateRequest(
+ clerkRequest,
+ createAuthenticateRequestOptions(clerkRequest, options),
+ );
- if (isRedirect(handlerResult)) {
- logger.debug('handlerResult is redirect');
- return serverRedirectWithAuth(clerkRequest, handlerResult, options);
- }
+ logger.debug('requestState', () => ({
+ status: requestState.status,
+ headers: JSON.stringify(Object.fromEntries(requestState.headers)),
+ reason: requestState.reason,
+ }));
+
+ const locationHeader = requestState.headers.get(constants.Headers.Location);
+ if (locationHeader) {
+ return new Response(null, { status: 307, headers: requestState.headers });
+ } else if (requestState.status === AuthStatus.Handshake) {
+ throw new Error('Clerk: handshake status without redirect');
+ }
+
+ const authObject = requestState.toAuth();
+ logger.debug('auth', () => ({ auth: authObject, debug: authObject.debug() }));
+
+ const redirectToSignIn = createMiddlewareRedirectToSignIn(clerkRequest);
+ const protect = createMiddlewareProtect(clerkRequest, authObject, redirectToSignIn);
+ const authObjWithMethods: ClerkMiddlewareAuthObject = Object.assign(authObject, { protect, redirectToSignIn });
+
+ let handlerResult: Response = NextResponse.next();
+ try {
+ handlerResult =
+ (await clerkClientStorage.run(
+ options,
+ async () => await handler?.(() => authObjWithMethods, request, event),
+ )) || handlerResult;
+ } catch (e: any) {
+ handlerResult = handleControlFlowErrors(e, clerkRequest, requestState);
+ }
+
+ // TODO @nikos: we need to make this more generic
+ // and move the logic in clerk/backend
+ if (requestState.headers) {
+ requestState.headers.forEach((value, key) => {
+ handlerResult.headers.append(key, value);
+ });
+ }
+
+ if (isRedirect(handlerResult)) {
+ logger.debug('handlerResult is redirect');
+ return serverRedirectWithAuth(clerkRequest, handlerResult, options);
+ }
+
+ if (options.debug) {
+ setRequestHeadersOnNextResponse(handlerResult, clerkRequest, { [constants.Headers.EnableDebug]: 'true' });
+ }
+
+ decorateRequest(clerkRequest, handlerResult, requestState, options);
+
+ return handlerResult;
+ };
- if (options.debug) {
- setRequestHeadersOnNextResponse(handlerResult, clerkRequest, { [constants.Headers.EnableDebug]: 'true' });
+ // If we have a request and event, we're being called as a middleware directly
+ // eg, export default clerkMiddleware;
+ if (request && event) {
+ return nextMiddleware(request, event);
}
- decorateRequest(clerkRequest, handlerResult, requestState, { secretKey, signInUrl, signUpUrl, publishableKey });
-
- return handlerResult;
- };
-
- // If we have a request and event, we're being called as a middleware directly
- // eg, export default clerkMiddleware;
- if (request && event) {
- return nextMiddleware(request, event);
- }
-
- // Otherwise, return a middleware that can be called with a request and event
- // eg, export default clerkMiddleware(auth => { ... });
- return nextMiddleware;
+ // Otherwise, return a middleware that can be called with a request and event
+ // eg, export default clerkMiddleware(auth => { ... });
+ return nextMiddleware;
+ });
});
const parseRequestAndEvent = (args: unknown[]) => {