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[]) => {