Skip to content

Commit c5b78fb

Browse files
committed
feat(backend,nextjs,remix,clerk-sdk-node): Support new secretKey prop
Add support for the new secretKey prop & CLERK_SECRET_KEY env variable and flag the old apiKey as deprecated
1 parent eb21e42 commit c5b78fb

16 files changed

+135
-55
lines changed

packages/backend/src/api/factory.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ import {
1616
import { buildRequest } from './request';
1717

1818
export type CreateBackendApiOptions = {
19-
/* Backend API key */
20-
apiKey: string;
19+
/**
20+
* Backend API key
21+
* @deprecated Use `secretKey` instead.
22+
*/
23+
apiKey?: string;
24+
/* Secret Key */
25+
secretKey?: string;
2126
/* Backend API URL */
2227
apiUrl?: string;
2328
/* Backend API version */

packages/backend/src/api/request.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,21 @@ const withLegacyReturn =
6262

6363
export function buildRequest(options: CreateBackendApiOptions) {
6464
const request = async <T>(requestOptions: ClerkBackendApiRequestOptions): Promise<ClerkBackendApiResponse<T>> => {
65-
const { apiKey, apiUrl = API_URL, apiVersion = API_VERSION, userAgent = USER_AGENT, httpOptions = {} } = options;
65+
const {
66+
apiKey,
67+
secretKey,
68+
apiUrl = API_URL,
69+
apiVersion = API_VERSION,
70+
userAgent = USER_AGENT,
71+
httpOptions = {},
72+
} = options;
6673
const { path, method, queryParams, headerParams, bodyParams } = requestOptions;
6774

68-
if (!apiKey) {
75+
const key = secretKey || apiKey;
76+
77+
if (!key) {
6978
throw Error(
70-
'Missing Clerk API Key. Go to https://dashboard.clerk.dev and get your Clerk API Key for your instance.',
79+
'Missing Clerk Secret Key or API Key. Go to https://dashboard.clerk.dev and get your key for your instance.',
7180
);
7281
}
7382

@@ -90,7 +99,7 @@ export function buildRequest(options: CreateBackendApiOptions) {
9099

91100
// Build headers
92101
const headers = {
93-
Authorization: `Bearer ${apiKey}`,
102+
Authorization: `Bearer ${key}`,
94103
'Content-Type': 'application/json',
95104
'Clerk-Backend-SDK': userAgent,
96105
...headerParams,

packages/backend/src/tokens/authObjects.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ type CreateAuthObjectDebug = (data?: AuthObjectDebugData) => AuthObjectDebug;
88
type AuthObjectDebug = () => unknown;
99

1010
export type SignedInAuthObjectOptions = {
11-
apiKey: string;
11+
/**
12+
* @deprecated Use `secretKey` instead.
13+
*/
14+
apiKey?: string;
15+
secretKey?: string;
1216
apiUrl: string;
1317
apiVersion: string;
1418
token: string;
@@ -71,10 +75,11 @@ export function signedInAuthObject(
7175
org_slug: orgSlug,
7276
sub: userId,
7377
} = sessionClaims;
74-
const { apiKey, apiUrl, apiVersion, token, session, user, organization } = options;
78+
const { apiKey, secretKey, apiUrl, apiVersion, token, session, user, organization } = options;
7579

7680
const { sessions } = createBackendApiClient({
7781
apiKey,
82+
secretKey,
7883
apiUrl,
7984
apiVersion,
8085
});

packages/backend/src/tokens/errors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export enum TokenVerificationErrorAction {
2525
ContactSupport = 'Contact support@clerk.dev',
2626
EnsureClerkJWT = 'Make sure that this is a valid Clerk generate JWT.',
2727
SetClerkJWTKey = 'Set the CLERK_JWT_KEY environment variable.',
28-
SetClerkAPIKey = 'Set the CLERK_API_KEY environment variable.',
28+
SetClerkSecretKeyOrAPIKey = 'Set the CLERK_SECRET_KEY or CLERK_API_KEY environment variable.',
2929
}
3030

3131
export class TokenVerificationError extends Error {

packages/backend/src/tokens/factory.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import {
1414

1515
export type CreateAuthenticateRequestOptions = {
1616
options: Partial<
17-
Pick<AuthenticateRequestOptions, 'apiKey' | 'apiUrl' | 'apiVersion' | 'frontendApi' | 'publishableKey' | 'jwtKey'>
17+
Pick<
18+
AuthenticateRequestOptions,
19+
'apiKey' | 'secretKey' | 'apiUrl' | 'apiVersion' | 'frontendApi' | 'publishableKey' | 'jwtKey'
20+
>
1821
>;
1922
apiClient: ApiClient;
2023
};
@@ -23,6 +26,7 @@ export function createAuthenticateRequest(params: CreateAuthenticateRequestOptio
2326
const { apiClient } = params;
2427
const {
2528
apiKey: buildtimeApiKey = '',
29+
secretKey: buildtimeSecretKey = '',
2630
jwtKey: buildtimeJwtKey = '',
2731
apiUrl = API_URL,
2832
apiVersion = API_VERSION,
@@ -32,6 +36,7 @@ export function createAuthenticateRequest(params: CreateAuthenticateRequestOptio
3236

3337
const authenticateRequest = ({
3438
apiKey: runtimeApiKey,
39+
secretKey: runtimeSecretKey,
3540
frontendApi: runtimeFrontendApi,
3641
publishableKey: runtimePublishableKey,
3742
jwtKey: runtimeJwtKey,
@@ -40,6 +45,7 @@ export function createAuthenticateRequest(params: CreateAuthenticateRequestOptio
4045
return authenticateRequestOriginal({
4146
...rest,
4247
apiKey: runtimeApiKey || buildtimeApiKey,
48+
secretKey: runtimeSecretKey || buildtimeSecretKey,
4349
apiUrl,
4450
apiVersion,
4551
frontendApi: runtimeFrontendApi || buildtimeFrontendApi,

packages/backend/src/tokens/keys.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,21 @@ export type LoadClerkJWKFromRemoteOptions = {
8989
} & (
9090
| {
9191
apiKey: string;
92+
secretKey?: never;
9293
apiUrl?: string;
9394
apiVersion?: string;
9495
issuer?: never;
9596
}
9697
| {
9798
apiKey?: never;
99+
secretKey: string;
100+
apiUrl?: string;
101+
apiVersion?: string;
102+
issuer?: never;
103+
}
104+
| {
105+
apiKey?: never;
106+
secretKey?: never;
98107
apiUrl?: never;
99108
apiVersion?: never;
100109
issuer: string;
@@ -116,6 +125,7 @@ export type LoadClerkJWKFromRemoteOptions = {
116125
*/
117126
export async function loadClerkJWKFromRemote({
118127
apiKey,
128+
secretKey,
119129
apiUrl,
120130
apiVersion,
121131
issuer,
@@ -125,9 +135,10 @@ export async function loadClerkJWKFromRemote({
125135
}: LoadClerkJWKFromRemoteOptions): Promise<JsonWebKey> {
126136
if (skipJwksCache || !getFromCache(kid)) {
127137
let fetcher;
138+
const key = secretKey || apiKey || '';
128139

129140
if (apiUrl) {
130-
fetcher = () => fetchJWKSFromBAPI(apiUrl, apiKey, apiVersion);
141+
fetcher = () => fetchJWKSFromBAPI(apiUrl, key, apiVersion);
131142
} else if (issuer) {
132143
fetcher = () => fetchJWKSFromFAPI(issuer);
133144
} else {
@@ -181,11 +192,12 @@ async function fetchJWKSFromFAPI(issuer: string) {
181192
return response.json();
182193
}
183194

184-
async function fetchJWKSFromBAPI(apiUrl: string = API_URL, apiKey: string, apiVersion: string = API_VERSION) {
185-
if (!apiKey) {
195+
async function fetchJWKSFromBAPI(apiUrl: string = API_URL, key: string, apiVersion: string = API_VERSION) {
196+
if (!key) {
186197
throw new TokenVerificationError({
187-
action: TokenVerificationErrorAction.SetClerkAPIKey,
188-
message: 'Missing Clerk API Key. Go to https://dashboard.clerk.dev and get your Clerk API Key for your instance.',
198+
action: TokenVerificationErrorAction.SetClerkSecretKeyOrAPIKey,
199+
message:
200+
'Missing Clerk Secret Key or API Key. Go to https://dashboard.clerk.dev and get your key for your instance.',
189201
reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad,
190202
});
191203
}
@@ -195,7 +207,7 @@ async function fetchJWKSFromBAPI(apiUrl: string = API_URL, apiKey: string, apiVe
195207

196208
const response = await runtime.fetch(url.href, {
197209
headers: {
198-
Authorization: `Bearer ${apiKey}`,
210+
Authorization: `Bearer ${key}`,
199211
'Content-Type': 'application/json',
200212
},
201213
});

packages/backend/src/tokens/request.ts

+30-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export type LoadResourcesOptions = {
2020
loadOrganization?: boolean;
2121
};
2222

23-
export type RequiredVerifyTokenOptions = Required<Pick<VerifyTokenOptions, 'apiKey' | 'apiUrl' | 'apiVersion'>>;
23+
export type RequiredVerifyTokenOptions = Required<
24+
Pick<VerifyTokenOptions, 'apiKey' | 'secretKey' | 'apiUrl' | 'apiVersion'>
25+
>;
2426

2527
export type OptionalVerifyTokenOptions = Partial<
2628
Pick<VerifyTokenOptions, 'authorizedParties' | 'clockSkewInSeconds' | 'jwksCacheTtlInMs' | 'skipJwksCache' | 'jwtKey'>
@@ -192,6 +194,7 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
192194

193195
const {
194196
apiKey,
197+
secretKey,
195198
apiUrl,
196199
apiVersion,
197200
cookieToken,
@@ -203,7 +206,12 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
203206
loadOrganization,
204207
} = options;
205208

206-
const { sessions, users, organizations } = createBackendApiClient({ apiKey, apiUrl, apiVersion });
209+
const { sessions, users, organizations } = createBackendApiClient({
210+
apiKey,
211+
secretKey,
212+
apiUrl,
213+
apiVersion,
214+
});
207215

208216
const [sessionResp, userResp, organizationResp] = await Promise.all([
209217
loadSession ? sessions.getSession(sessionId) : Promise.resolve(undefined),
@@ -222,6 +230,7 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
222230
sessionClaims,
223231
{
224232
apiKey,
233+
secretKey,
225234
apiUrl,
226235
apiVersion,
227236
token: cookieToken || headerToken || '',
@@ -289,8 +298,9 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
289298
// prevent interstitial rendering
290299
// In production, script requests will be missing both uat and session cookies, which will be
291300
// automatically treated as signed out. This exception is needed for development, because the any // missing uat throws an interstitial in development.
292-
const nonBrowserRequestInDevRule: InterstitialRule = ({ apiKey, userAgent }) => {
293-
if (isDevelopmentFromApiKey(apiKey) && !userAgent?.startsWith('Mozilla/')) {
301+
const nonBrowserRequestInDevRule: InterstitialRule = ({ apiKey, secretKey, userAgent }) => {
302+
const key = secretKey || apiKey;
303+
if (isDevelopmentFromApiKey(key) && !userAgent?.startsWith('Mozilla/')) {
294304
return signedOut(RequestErrorReason.HeaderMissingNonBrowser);
295305
}
296306
return undefined;
@@ -319,8 +329,10 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
319329
return undefined;
320330
};
321331

322-
const potentialFirstLoadInDevWhenUATMissing: InterstitialRule = ({ apiKey, clientUat }) => {
323-
const res = isDevelopmentFromApiKey(apiKey);
332+
const potentialFirstLoadInDevWhenUATMissing: InterstitialRule = ({ apiKey, secretKey, clientUat }) => {
333+
const key = secretKey || apiKey;
334+
335+
const res = isDevelopmentFromApiKey(key);
324336
if (res && !clientUat) {
325337
return interstitial(RequestErrorReason.CookieUATMissing);
326338
}
@@ -329,6 +341,7 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
329341

330342
const potentialRequestAfterSignInOrOurFromClerkHostedUiInDev: InterstitialRule = ({
331343
apiKey,
344+
secretKey,
332345
referrer,
333346
host,
334347
forwardedHost,
@@ -338,15 +351,23 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
338351
const crossOriginReferrer =
339352
referrer &&
340353
checkCrossOrigin({ originURL: new URL(referrer), host, forwardedHost, forwardedPort, forwardedProto });
354+
const key = secretKey || apiKey;
341355

342-
if (isDevelopmentFromApiKey(apiKey) && crossOriginReferrer) {
356+
if (isDevelopmentFromApiKey(key) && crossOriginReferrer) {
343357
return interstitial(RequestErrorReason.CrossOriginReferrer);
344358
}
345359
return undefined;
346360
};
347361

348-
const potentialFirstRequestOnProductionEnvironment: InterstitialRule = ({ apiKey, clientUat, cookieToken }) => {
349-
if (isProductionFromApiKey(apiKey) && !clientUat && !cookieToken) {
362+
const potentialFirstRequestOnProductionEnvironment: InterstitialRule = ({
363+
apiKey,
364+
secretKey,
365+
clientUat,
366+
cookieToken,
367+
}) => {
368+
const key = secretKey || apiKey;
369+
370+
if (isProductionFromApiKey(key) && !clientUat && !cookieToken) {
350371
return signedOut(RequestErrorReason.CookieAndUATMissing);
351372
}
352373
return undefined;

packages/backend/src/tokens/verify.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ export type VerifyTokenOptions = Pick<
1212
'authorizedParties' | 'audience' | 'issuer' | 'clockSkewInSeconds'
1313
> & { jwtKey?: string } & Pick<
1414
LoadClerkJWKFromRemoteOptions,
15-
'apiKey' | 'apiUrl' | 'apiVersion' | 'jwksCacheTtlInMs' | 'skipJwksCache'
15+
'apiKey' | 'secretKey' | 'apiUrl' | 'apiVersion' | 'jwksCacheTtlInMs' | 'skipJwksCache'
1616
>;
1717

1818
export async function verifyToken(token: string, options: VerifyTokenOptions): Promise<JwtPayload> {
1919
const {
2020
apiKey,
21+
secretKey,
2122
apiUrl,
2223
apiVersion,
2324
authorizedParties,
@@ -35,20 +36,16 @@ export async function verifyToken(token: string, options: VerifyTokenOptions): P
3536

3637
if (jwtKey) {
3738
key = loadClerkJWKFromLocal(jwtKey);
38-
} else if (apiKey) {
39+
} else if (apiKey || secretKey) {
3940
// TODO: Fetch JWKS from Frontend API instead of Backend API
4041
//
4142
// Currently JWKS are fetched from Backend API without using the JWT issuer. This should change
4243
// with the new Backend key format so that the API Key is not required for token verification and
4344
// the jwks retrieval is driven from the current JWT header.
44-
key = await loadClerkJWKFromRemote({
45-
apiUrl,
46-
apiKey,
47-
apiVersion,
48-
kid,
49-
jwksCacheTtlInMs,
50-
skipJwksCache,
51-
});
45+
const opts = apiKey
46+
? ({ apiUrl, apiKey, apiVersion, kid, jwksCacheTtlInMs, skipJwksCache } as LoadClerkJWKFromRemoteOptions)
47+
: ({ apiUrl, secretKey, apiVersion, kid, jwksCacheTtlInMs, skipJwksCache } as LoadClerkJWKFromRemoteOptions);
48+
key = await loadClerkJWKFromRemote(opts);
5249
} else {
5350
throw new TokenVerificationError({
5451
action: TokenVerificationErrorAction.SetClerkJWTKey,

packages/nextjs/src/middleware/utils/authenticateRequest.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { GetServerSidePropsContext } from 'next';
22

3-
import { API_KEY, clerkClient, FRONTEND_API, PUBLISHABLE_KEY } from '../../server';
3+
import { API_KEY, clerkClient, FRONTEND_API, PUBLISHABLE_KEY, SECRET_KEY } from '../../server';
44
import { WithServerSideAuthOptions } from '../types';
55

66
/**
@@ -15,6 +15,7 @@ export async function authenticateRequest(ctx: GetServerSidePropsContext, opts:
1515
return clerkClient.authenticateRequest({
1616
...opts,
1717
apiKey: API_KEY,
18+
secretKey: SECRET_KEY,
1819
frontendApi: FRONTEND_API,
1920
publishableKey: PUBLISHABLE_KEY,
2021
cookieToken,

packages/nextjs/src/server/clerk.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { Clerk } from '@clerk/backend';
22

33
export const API_URL = process.env.CLERK_API_URL || 'https://api.clerk.dev';
44
export const API_VERSION = process.env.CLERK_API_VERSION || 'v1';
5-
export const API_KEY = process.env.CLERK_SECRET_KEY || process.env.CLERK_API_KEY || '';
5+
export const API_KEY = process.env.CLERK_API_KEY || '';
6+
export const SECRET_KEY = process.env.CLERK_SECRET_KEY || '';
67
export const FRONTEND_API = process.env.NEXT_PUBLIC_CLERK_FRONTEND_API || '';
78
export const PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || '';
89

packages/nextjs/src/server/withClerkMiddleware.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NextMiddleware, NextMiddlewareResult } from 'next/dist/server/web/types
33
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server';
44

55
import { constants as nextConstants } from '../constants';
6-
import { API_KEY, API_URL, clerkClient, FRONTEND_API, PUBLISHABLE_KEY } from './clerk';
6+
import { API_KEY, API_URL, clerkClient, FRONTEND_API, PUBLISHABLE_KEY, SECRET_KEY } from './clerk';
77
import {
88
getCookie,
99
nextJsVersionCanOverrideRequestHeaders,
@@ -32,8 +32,8 @@ export const withClerkMiddleware: WithClerkMiddleware = (...args: unknown[]) =>
3232

3333
const requestState = await clerkClient.authenticateRequest({
3434
...opts,
35-
// TODO: Make apiKey optional
3635
apiKey: API_KEY,
36+
secretKey: SECRET_KEY,
3737
frontendApi: FRONTEND_API,
3838
publishableKey: PUBLISHABLE_KEY,
3939
cookieToken,

packages/remix/src/errors.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) =>
6666
})
6767
`);
6868

69-
export const noApiKeyError = createErrorMessage(`
70-
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
71-
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
69+
export const noSecretKeyOrApiKeyError = createErrorMessage(`
70+
A secretKey or apiKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
71+
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY or CLERK_API_KEY variable to your config.
7272
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
7373
`);

0 commit comments

Comments
 (0)