From d4f5b8416c79a35be8ba8d73ebfdb0a90bd09254 Mon Sep 17 00:00:00 2001 From: Jack Adam Date: Thu, 13 Feb 2025 14:39:35 +0000 Subject: [PATCH 1/5] fix(nextjs): accept async callback for ClerkMiddlewareOptionsCallback --- .changeset/fast-teachers-attack.md | 5 +++++ packages/nextjs/src/server/clerkMiddleware.ts | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/fast-teachers-attack.md diff --git a/.changeset/fast-teachers-attack.md b/.changeset/fast-teachers-attack.md new file mode 100644 index 00000000000..dc63cd90513 --- /dev/null +++ b/.changeset/fast-teachers-attack.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': patch +--- + +Update ClerkMiddlewareOptionsCallback to also accept an async function diff --git a/packages/nextjs/src/server/clerkMiddleware.ts b/packages/nextjs/src/server/clerkMiddleware.ts index 17ddcfcf965..0d7c88a0941 100644 --- a/packages/nextjs/src/server/clerkMiddleware.ts +++ b/packages/nextjs/src/server/clerkMiddleware.ts @@ -59,7 +59,7 @@ export type ClerkMiddlewareOptions = AuthenticateRequestOptions & { debug?: boolean; }; -type ClerkMiddlewareOptionsCallback = (req: NextRequest) => ClerkMiddlewareOptions; +type ClerkMiddlewareOptionsCallback = (req: NextRequest) => ClerkMiddlewareOptions | Promise; /** * Middleware for Next.js that handles authentication and authorization with Clerk. @@ -102,7 +102,7 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { return clerkMiddlewareRequestDataStorage.run(clerkMiddlewareRequestDataStore, () => { const baseNextMiddleware: NextMiddleware = withLogger('clerkMiddleware', logger => async (request, event) => { // Handles the case where `options` is a callback function to dynamically access `NextRequest` - const resolvedParams = typeof params === 'function' ? params(request) : params; + const resolvedParams = typeof params === 'function' ? await params(request) : params; const keyless = getKeylessCookieValue(name => request.cookies.get(name)?.value); @@ -223,7 +223,7 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { return returnBackFromKeylessSync(request); } - const resolvedParams = typeof params === 'function' ? params(request) : params; + const resolvedParams = typeof params === 'function' ? await params(request) : params; const keyless = getKeylessCookieValue(name => request.cookies.get(name)?.value); const isMissingPublishableKey = !(resolvedParams.publishableKey || PUBLISHABLE_KEY || keyless?.publishableKey); /** From 31facf760474412ab049c59833817dc1af601ea8 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:00:40 -0300 Subject: [PATCH 2/5] Add unit tests for async/sync callbacks --- .../server/__tests__/clerkMiddleware.test.ts | 80 ++++++++++++++----- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts index 4f1a5d496a1..4eccd1ac010 100644 --- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts +++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts @@ -238,31 +238,67 @@ describe('clerkMiddleware(params)', () => { expect(decryptedData).toEqual(options); }); - it('allows access to request object to dynamically define options', async () => { - const options = { - secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx', - publishableKey: 'pk_test_xxxxxxxxxxxxx', - signInUrl: '/foo', - signUpUrl: '/bar', - }; - const resp = await clerkMiddleware( - () => { - return NextResponse.next(); - }, - req => ({ - ...options, - domain: req.nextUrl.host, - }), - )(mockRequest({ url: '/sign-in' }), {} as NextFetchEvent); - expect(resp?.status).toEqual(200); + describe('allows access to request object to define options via callback', () => { + it('with synchronously callback', async () => { + const options = { + secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx', + publishableKey: 'pk_test_xxxxxxxxxxxxx', + signInUrl: '/foo', + signUpUrl: '/bar', + }; + const resp = await clerkMiddleware( + () => { + return NextResponse.next(); + }, + req => ({ + ...options, + domain: req.nextUrl.host, + }), + )(mockRequest({ url: '/sign-in' }), {} as NextFetchEvent); + expect(resp?.status).toEqual(200); - const requestData = resp?.headers.get('x-middleware-request-x-clerk-request-data'); - assert.ok(requestData); + const requestData = resp?.headers.get('x-middleware-request-x-clerk-request-data'); + assert.ok(requestData); - const decryptedData = decryptClerkRequestData(requestData); + const decryptedData = decryptClerkRequestData(requestData); - expect(resp?.headers.get('x-middleware-request-x-clerk-request-data')).toBeDefined(); - expect(decryptedData).toEqual({ ...options, domain: 'www.clerk.com' }); + expect(resp?.headers.get('x-middleware-request-x-clerk-request-data')).toBeDefined(); + expect(decryptedData).toEqual({ ...options, domain: 'www.clerk.com' }); + }); + + it('with asynchronously callback', async () => { + const options = { + secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx', + publishableKey: 'pk_test_xxxxxxxxxxxxx', + signInUrl: '/foo', + signUpUrl: '/bar', + }; + + const mockFetchOptionsExternalStore = (_req: NextRequest) => Promise.resolve(options); + + const resp = await clerkMiddleware( + () => { + return NextResponse.next(); + }, + async req => { + const resolvedOptions = await mockFetchOptionsExternalStore(req); + + return { + ...resolvedOptions, + domain: req.nextUrl.host, + }; + }, + )(mockRequest({ url: '/sign-in' }), {} as NextFetchEvent); + expect(resp?.status).toEqual(200); + + const requestData = resp?.headers.get('x-middleware-request-x-clerk-request-data'); + assert.ok(requestData); + + const decryptedData = decryptClerkRequestData(requestData); + + expect(resp?.headers.get('x-middleware-request-x-clerk-request-data')).toBeDefined(); + expect(decryptedData).toEqual({ ...options, domain: 'www.clerk.com' }); + }); }); describe('auth().redirectToSignIn()', () => { From fe8b23d2fa6c6ffe112c1596296b6167c8116cce Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:10:01 -0300 Subject: [PATCH 3/5] Update changeset --- .changeset/fast-teachers-attack.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.changeset/fast-teachers-attack.md b/.changeset/fast-teachers-attack.md index dc63cd90513..17114d1bf48 100644 --- a/.changeset/fast-teachers-attack.md +++ b/.changeset/fast-teachers-attack.md @@ -2,4 +2,16 @@ '@clerk/nextjs': patch --- -Update ClerkMiddlewareOptionsCallback to also accept an async function +Update `clerkMiddleware` request callback to also accept an asynchronous function + +```ts +export default clerkMiddleware( + (auth, req) => { + // Add your middleware checks + }, + async (req) => { + const options = await getOptions(req) + return options; + }, +) +``` From eeb8df59578595e46caff2e8f5456d858b0297ff Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:12:07 -0300 Subject: [PATCH 4/5] Update changeset --- .changeset/fast-teachers-attack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fast-teachers-attack.md b/.changeset/fast-teachers-attack.md index 17114d1bf48..fc3792e91e7 100644 --- a/.changeset/fast-teachers-attack.md +++ b/.changeset/fast-teachers-attack.md @@ -2,7 +2,7 @@ '@clerk/nextjs': patch --- -Update `clerkMiddleware` request callback to also accept an asynchronous function +Update `clerkMiddleware` request callback to accept an asynchronous function ```ts export default clerkMiddleware( From c8bf4b874f12763adce1fc9ce932d233c0151156 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:12:59 -0300 Subject: [PATCH 5/5] Fix typo on test cases --- packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts index 4eccd1ac010..f5f2868be04 100644 --- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts +++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts @@ -239,7 +239,7 @@ describe('clerkMiddleware(params)', () => { }); describe('allows access to request object to define options via callback', () => { - it('with synchronously callback', async () => { + it('with synchronous callback', async () => { const options = { secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx', publishableKey: 'pk_test_xxxxxxxxxxxxx', @@ -266,7 +266,7 @@ describe('clerkMiddleware(params)', () => { expect(decryptedData).toEqual({ ...options, domain: 'www.clerk.com' }); }); - it('with asynchronously callback', async () => { + it('with asynchronous callback', async () => { const options = { secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx', publishableKey: 'pk_test_xxxxxxxxxxxxx',