Skip to content

Commit 25efe99

Browse files
committed
fix(fastify): use runtime keys for auth client
1 parent 54ddc2f commit 25efe99

15 files changed

Lines changed: 176 additions & 27 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@clerk/fastify': patch
3+
'@clerk/express': patch
4+
'@clerk/astro': patch
5+
'@clerk/nuxt': patch
6+
'@clerk/tanstack-react-start': patch
7+
'@clerk/react-router': patch
8+
---
9+
10+
Use runtime middleware keys when creating the request client used by server-side auth middleware, so nonce handshake payload exchange works when keys are passed directly to framework middleware.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createClerkClient } from '@clerk/backend';
2+
import { describe, expect, test, vi } from 'vitest';
3+
4+
import { clerkClient } from '../clerk-client';
5+
6+
vi.mock('@clerk/backend', () => ({
7+
createClerkClient: vi.fn().mockReturnValue({}),
8+
}));
9+
10+
describe('clerkClient', () => {
11+
test('passes runtime options to createClerkClient', () => {
12+
clerkClient({ locals: {} } as any, {
13+
secretKey: 'sk_test_runtime',
14+
publishableKey: 'pk_test_runtime',
15+
});
16+
17+
expect(vi.mocked(createClerkClient)).toHaveBeenCalledWith(
18+
expect.objectContaining({
19+
secretKey: 'sk_test_runtime',
20+
publishableKey: 'pk_test_runtime',
21+
}),
22+
);
23+
});
24+
});

packages/astro/src/server/clerk-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ const createClerkClientWithOptions: CreateClerkClientWithOptions = (context, opt
2929
...options,
3030
});
3131

32-
const clerkClient = (context: APIContext) => createClerkClientWithOptions(context);
32+
const clerkClient = (context: APIContext, options?: ClerkOptions) => createClerkClientWithOptions(context, options);
3333

3434
export { clerkClient };

packages/astro/src/server/clerk-middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]): any => {
117117
}
118118
}
119119

120-
const requestState = await clerkClient(context).authenticateRequest(
120+
const requestState = await clerkClient(context, keylessOptions).authenticateRequest(
121121
clerkRequest,
122122
createAuthenticateRequestOptions(clerkRequest, keylessOptions, context),
123123
);

packages/express/src/__tests__/clerkMiddleware.test.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ describe('clerkMiddleware', () => {
216216
expect(forwarded).not.toHaveProperty('frontendApiProxy');
217217
});
218218

219-
describe('apiUrl/apiVersion default-client construction', () => {
219+
describe('default-client construction overrides', () => {
220220
beforeEach(() => {
221221
mockCreateClerkClient.mockClear();
222222
});
@@ -243,8 +243,22 @@ describe('clerkMiddleware', () => {
243243
expect(mockCreateClerkClient).toHaveBeenCalledWith(expect.objectContaining({ apiVersion: 'v2' }));
244244
});
245245

246-
it('does not call createClerkClient at construction when apiUrl/apiVersion are not set', () => {
247-
authenticateAndDecorateRequest({ secretKey: 'sk_test_....' });
246+
it('builds a per-middleware ClerkClient with runtime keys when no custom clerkClient is supplied', () => {
247+
authenticateAndDecorateRequest({
248+
secretKey: 'sk_test_runtime',
249+
publishableKey: 'pk_test_runtime',
250+
});
251+
252+
expect(mockCreateClerkClient).toHaveBeenCalledWith(
253+
expect.objectContaining({
254+
secretKey: 'sk_test_runtime',
255+
publishableKey: 'pk_test_runtime',
256+
}),
257+
);
258+
});
259+
260+
it('does not call createClerkClient at construction when client construction overrides are not set', () => {
261+
authenticateAndDecorateRequest({});
248262

249263
expect(mockCreateClerkClient).not.toHaveBeenCalled();
250264
});

packages/express/src/authenticateRequest.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,21 +112,26 @@ const absoluteProxyUrl = (relativeOrAbsoluteUrl: string, baseUrl: string): strin
112112
return new URL(relativeOrAbsoluteUrl, baseUrl).toString();
113113
};
114114

115-
// `apiUrl` and `apiVersion` are pinned at client construction time inside
116-
// `@clerk/backend`'s `createAuthenticateRequest` factory (build-time values
117-
// override runtime ones). The default singleton in `./clerkClient` is built
115+
// Some options are pinned at client construction time inside `@clerk/backend`'s
116+
// `createAuthenticateRequest` factory, including the API client used for nonce
117+
// handshake payload exchange. The default singleton in `./clerkClient` is built
118118
// from env only, so passing these via `clerkMiddleware()` would be silently
119-
// ignored. When the caller hasn't supplied their own `clerkClient` but did
120-
// pass `apiUrl`/`apiVersion`, build a per-middleware client with those values.
119+
// ignored. When the caller hasn't supplied their own `clerkClient` but did pass
120+
// client construction options, build a per-middleware client with those values.
121121
const resolveDefaultClerkClient = (options: ClerkMiddlewareOptions) => {
122-
if (!options.apiUrl && !options.apiVersion) {
122+
const { apiUrl, apiVersion, secretKey, machineSecretKey, publishableKey, jwtKey } = options;
123+
if (!apiUrl && !apiVersion && !secretKey && !machineSecretKey && !publishableKey && !jwtKey) {
123124
return defaultClerkClient;
124125
}
125126
const env = { ...loadApiEnv(), ...loadClientEnv() };
126127
return createClerkClient({
127128
...env,
128-
...(options.apiUrl ? { apiUrl: options.apiUrl } : {}),
129-
...(options.apiVersion ? { apiVersion: options.apiVersion } : {}),
129+
...(apiUrl ? { apiUrl } : {}),
130+
...(apiVersion ? { apiVersion } : {}),
131+
...(secretKey ? { secretKey } : {}),
132+
...(machineSecretKey ? { machineSecretKey } : {}),
133+
...(publishableKey ? { publishableKey } : {}),
134+
...(jwtKey ? { jwtKey } : {}),
130135
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
131136
});
132137
};

packages/fastify/src/__tests__/withClerkMiddleware.test.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
44

55
import { clerkPlugin, getAuth } from '../index';
66

7-
const authenticateRequestMock = vi.fn();
7+
const { authenticateRequestMock, createClerkClientMock } = vi.hoisted(() => {
8+
const authenticateRequestMock = vi.fn();
9+
const createClerkClientMock = vi.fn(() => {
10+
return {
11+
authenticateRequest: (...args: any) => authenticateRequestMock(...args),
12+
};
13+
});
14+
15+
return { authenticateRequestMock, createClerkClientMock };
16+
});
817

918
vi.mock('@clerk/backend', async () => {
1019
const actual = await vi.importActual('@clerk/backend');
1120
return {
1221
...actual,
13-
createClerkClient: () => {
14-
return {
15-
authenticateRequest: (...args: any) => authenticateRequestMock(...args),
16-
};
17-
},
22+
createClerkClient: (...args: any[]) => createClerkClientMock(...args),
1823
};
1924
});
2025

@@ -24,6 +29,38 @@ describe('withClerkMiddleware(options)', () => {
2429
vi.restoreAllMocks();
2530
});
2631

32+
test('creates the request client with plugin runtime keys', async () => {
33+
authenticateRequestMock.mockResolvedValueOnce({
34+
headers: new Headers(),
35+
toAuth: () => ({
36+
tokenType: 'session_token',
37+
}),
38+
});
39+
const fastify = Fastify();
40+
await fastify.register(clerkPlugin, {
41+
secretKey: 'runtime_secret_key',
42+
publishableKey: 'runtime_publishable_key',
43+
});
44+
45+
fastify.get('/', (request: FastifyRequest, reply: FastifyReply) => {
46+
const auth = getAuth(request);
47+
reply.send({ auth });
48+
});
49+
50+
const response = await fastify.inject({
51+
method: 'GET',
52+
path: '/',
53+
});
54+
55+
expect(response.statusCode).toEqual(200);
56+
expect(createClerkClientMock).toHaveBeenLastCalledWith(
57+
expect.objectContaining({
58+
secretKey: 'runtime_secret_key',
59+
publishableKey: 'runtime_publishable_key',
60+
}),
61+
);
62+
});
63+
2764
test('handles signin with Authorization Bearer', async () => {
2865
authenticateRequestMock.mockResolvedValueOnce({
2966
headers: new Headers(),

packages/fastify/src/withClerkMiddleware.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1+
import { createClerkClient } from '@clerk/backend';
12
import { AuthStatus } from '@clerk/backend/internal';
23
import { clerkFrontendApiProxy, DEFAULT_PROXY_PATH, stripTrailingSlashes } from '@clerk/backend/proxy';
34
import type { FastifyReply, FastifyRequest } from 'fastify';
45
import { Readable } from 'stream';
56

6-
import { clerkClient } from './clerkClient';
77
import * as constants from './constants';
88
import type { ClerkFastifyOptions } from './types';
99
import { fastifyRequestToRequest, requestToProxyRequest } from './utils';
1010

1111
export const withClerkMiddleware = (options: ClerkFastifyOptions) => {
1212
const frontendApiProxy = options.frontendApiProxy;
1313
const proxyPath = stripTrailingSlashes(frontendApiProxy?.path ?? DEFAULT_PROXY_PATH) || DEFAULT_PROXY_PATH;
14+
const publishableKey = options.publishableKey || constants.PUBLISHABLE_KEY;
15+
const secretKey = options.secretKey || constants.SECRET_KEY;
16+
const clerkClient = createClerkClient({
17+
...options,
18+
publishableKey,
19+
secretKey,
20+
machineSecretKey: options.machineSecretKey || constants.MACHINE_SECRET_KEY,
21+
apiUrl: options.apiUrl || constants.API_URL,
22+
apiVersion: options.apiVersion || constants.API_VERSION,
23+
jwtKey: options.jwtKey || constants.JWT_KEY,
24+
userAgent: options.userAgent || `${constants.SDK_METADATA.name}@${constants.SDK_METADATA.version}`,
25+
sdkMetadata: options.sdkMetadata || constants.SDK_METADATA,
26+
});
1427

1528
return async (fastifyRequest: FastifyRequest, reply: FastifyReply) => {
16-
const publishableKey = options.publishableKey || constants.PUBLISHABLE_KEY;
17-
const secretKey = options.secretKey || constants.SECRET_KEY;
18-
1929
// Handle Frontend API proxy requests and auto-derive proxyUrl
2030
let resolvedProxyUrl = options.proxyUrl;
2131
if (frontendApiProxy) {

packages/nuxt/src/runtime/server/__tests__/clerkClient.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,20 @@ describe('clerkClient', () => {
9090
}),
9191
);
9292
});
93+
94+
it('passes runtime options to createClerkClient', () => {
95+
mockRuntimeConfig();
96+
97+
clerkClient({} as any, {
98+
secretKey: 'sk_test_runtime',
99+
publishableKey: 'pk_test_runtime',
100+
});
101+
102+
expect(createClerkClientMock).toHaveBeenCalledWith(
103+
expect.objectContaining({
104+
secretKey: 'sk_test_runtime',
105+
publishableKey: 'pk_test_runtime',
106+
}),
107+
);
108+
});
93109
});

packages/nuxt/src/runtime/server/clerkClient.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ClerkOptions } from '@clerk/backend';
12
import { createClerkClient } from '@clerk/backend';
23
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
34
import { deprecated } from '@clerk/shared/deprecated';
@@ -28,7 +29,7 @@ function resolveApiVersion(runtimeConfig: ReturnType<typeof useRuntimeConfig>):
2829
return 'v1';
2930
}
3031

31-
export function clerkClient(event: H3Event) {
32+
export function clerkClient(event: H3Event, options?: ClerkOptions) {
3233
const runtimeConfig = useRuntimeConfig(event);
3334

3435
return createClerkClient({
@@ -51,5 +52,6 @@ export function clerkClient(event: H3Event) {
5152
version: PACKAGE_VERSION,
5253
environment: process.env.NODE_ENV,
5354
},
55+
...options,
5456
});
5557
}

0 commit comments

Comments
 (0)