Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/fast-teachers-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@clerk/nextjs': patch
---

Update `clerkMiddleware` request callback to accept an asynchronous function

```ts
export default clerkMiddleware(
(auth, req) => {
// Add your middleware checks
},
async (req) => {
const options = await getOptions(req)
return options;
},
)
```
80 changes: 58 additions & 22 deletions packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 synchronous 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 asynchronous 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()', () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/nextjs/src/server/clerkMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type ClerkMiddlewareOptions = AuthenticateRequestOptions & {
debug?: boolean;
};

type ClerkMiddlewareOptionsCallback = (req: NextRequest) => ClerkMiddlewareOptions;
type ClerkMiddlewareOptionsCallback = (req: NextRequest) => ClerkMiddlewareOptions | Promise<ClerkMiddlewareOptions>;

/**
* Middleware for Next.js that handles authentication and authorization with Clerk.
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
/**
Expand Down
Loading