Skip to content

Commit 0217e07

Browse files
djgouldclaude
andcommitted
feat(*): remove keyless bootstrap from astro, nuxt, react-router, and tanstack-react-start
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 7074adc commit 0217e07

47 files changed

Lines changed: 55 additions & 929 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@clerk/astro': minor
3+
'@clerk/nuxt': minor
4+
'@clerk/react-router': minor
5+
'@clerk/tanstack-react-start': minor
6+
---
7+
8+
In development, missing Clerk keys no longer activate keyless mode. When the framework's publishable/secret key environment variables are not set, the SDK now throws an error directing you to run `npx clerk@latest init`, which provisions a Clerk application and writes the keys to your env file. Existing apps with configured keys are unaffected.
Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import { test } from '@playwright/test';
1+
import { expect, test } from '@playwright/test';
22

33
import type { Application } from '../../models/application';
44
import { appConfigs } from '../../presets';
5-
import {
6-
testClaimedAppWithMissingKeys,
7-
testKeylessRemovedAfterEnvAndRestart,
8-
testToggleCollapsePopoverAndClaim,
9-
} from '../../testUtils/keylessHelpers';
105

116
const commonSetup = appConfigs.astro.node.clone();
127

@@ -21,34 +16,25 @@ test.describe('Keyless mode @astro', () => {
2116
});
2217

2318
let app: Application;
24-
let dashboardUrl = 'https://dashboard.clerk.com/';
2519

2620
test.beforeAll(async () => {
2721
app = await commonSetup.commit();
2822
await app.setup();
2923
await app.withEnv(appConfigs.envs.withKeyless);
30-
if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) {
31-
dashboardUrl = 'https://dashboard.clerkstage.dev/';
32-
}
3324
await app.dev();
3425
});
3526

3627
test.afterAll(async () => {
3728
await app?.teardown();
3829
});
3930

40-
test('Toggle collapse popover and claim.', async ({ page, context }) => {
41-
await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'astro' });
42-
});
43-
44-
test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({
31+
test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({
4532
page,
46-
context,
4733
}) => {
48-
await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl });
49-
});
50-
51-
test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => {
52-
await testKeylessRemovedAfterEnvAndRestart({ page, context, app });
34+
const response = await page.goto(`${app.serverUrl}/`);
35+
expect(response?.status()).toBe(500);
36+
const content = await page.content();
37+
expect(content).toContain('Publishable key is missing');
38+
expect(content).toContain('npx clerk@latest init');
5339
});
5440
});
Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import { test } from '@playwright/test';
1+
import { expect, test } from '@playwright/test';
22

33
import type { Application } from '../../models/application';
44
import { appConfigs } from '../../presets';
5-
import {
6-
testClaimedAppWithMissingKeys,
7-
testKeylessRemovedAfterEnvAndRestart,
8-
testToggleCollapsePopoverAndClaim,
9-
} from '../../testUtils/keylessHelpers';
105

116
const commonSetup = appConfigs.nuxt.node.clone();
127

@@ -21,35 +16,25 @@ test.describe('Keyless mode @nuxt', () => {
2116
});
2217

2318
let app: Application;
24-
let dashboardUrl = 'https://dashboard.clerk.com/';
2519

2620
test.beforeAll(async () => {
2721
app = await commonSetup.commit();
2822
await app.setup();
2923
await app.withEnv(appConfigs.envs.withKeyless);
30-
if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) {
31-
dashboardUrl = 'https://dashboard.clerkstage.dev/';
32-
}
3324
await app.dev();
3425
});
3526

3627
test.afterAll(async () => {
37-
// Keep files for debugging
3828
await app?.teardown();
3929
});
4030

41-
test('Toggle collapse popover and claim.', async ({ page, context }) => {
42-
await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'nuxt' });
43-
});
44-
45-
test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({
31+
test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({
4632
page,
47-
context,
4833
}) => {
49-
await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl });
50-
});
51-
52-
test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => {
53-
await testKeylessRemovedAfterEnvAndRestart({ page, context, app });
34+
const response = await page.goto(`${app.serverUrl}/`);
35+
expect(response?.status()).toBe(500);
36+
const content = await page.content();
37+
expect(content).toContain('Publishable key is missing');
38+
expect(content).toContain('npx clerk@latest init');
5439
});
5540
});
Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import { test } from '@playwright/test';
1+
import { expect, test } from '@playwright/test';
22

33
import type { Application } from '../../models/application';
44
import { appConfigs } from '../../presets';
5-
import {
6-
testClaimedAppWithMissingKeys,
7-
testKeylessRemovedAfterEnvAndRestart,
8-
testToggleCollapsePopoverAndClaim,
9-
} from '../../testUtils/keylessHelpers';
105

116
const commonSetup = appConfigs.reactRouter.reactRouterNode.clone();
127

@@ -21,35 +16,25 @@ test.describe('Keyless mode @react-router', () => {
2116
});
2217

2318
let app: Application;
24-
let dashboardUrl = 'https://dashboard.clerk.com/';
2519

2620
test.beforeAll(async () => {
2721
app = await commonSetup.commit();
2822
await app.setup();
2923
await app.withEnv(appConfigs.envs.withKeyless);
30-
if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) {
31-
dashboardUrl = 'https://dashboard.clerkstage.dev/';
32-
}
3324
await app.dev();
3425
});
3526

3627
test.afterAll(async () => {
37-
// Keep files for debugging
3828
await app?.teardown();
3929
});
4030

41-
test('Toggle collapse popover and claim.', async ({ page, context }) => {
42-
await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'react-router' });
43-
});
44-
45-
test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({
31+
test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({
4632
page,
47-
context,
4833
}) => {
49-
await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl });
50-
});
51-
52-
test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => {
53-
await testKeylessRemovedAfterEnvAndRestart({ page, context, app });
34+
const response = await page.goto(`${app.serverUrl}/`);
35+
expect(response?.status()).toBe(500);
36+
const content = await page.content();
37+
expect(content).toContain('A secretKey must be provided');
38+
expect(content).toContain('npx clerk@latest init');
5439
});
5540
});
Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
1-
import { test } from '@playwright/test';
1+
import { expect, test } from '@playwright/test';
22

33
import type { Application } from '../../models/application';
44
import { appConfigs } from '../../presets';
5-
import {
6-
testClaimedAppWithMissingKeys,
7-
testKeylessRemovedAfterEnvAndRestart,
8-
testToggleCollapsePopoverAndClaim,
9-
} from '../../testUtils/keylessHelpers';
105

11-
const commonSetup = appConfigs.reactRouter.reactRouterNode.clone();
6+
const commonSetup = appConfigs.tanstack.reactStart.clone();
127

13-
test.describe('Keyless mode @react-router', () => {
8+
test.describe('Keyless mode @tanstack-react-start', () => {
149
test.describe.configure({ mode: 'serial' });
1510
test.setTimeout(90_000);
1611

@@ -21,35 +16,25 @@ test.describe('Keyless mode @react-router', () => {
2116
});
2217

2318
let app: Application;
24-
let dashboardUrl = 'https://dashboard.clerk.com/';
2519

2620
test.beforeAll(async () => {
2721
app = await commonSetup.commit();
2822
await app.setup();
2923
await app.withEnv(appConfigs.envs.withKeyless);
30-
if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) {
31-
dashboardUrl = 'https://dashboard.clerkstage.dev/';
32-
}
3324
await app.dev();
3425
});
3526

3627
test.afterAll(async () => {
37-
// Keep files for debugging
3828
await app?.teardown();
3929
});
4030

41-
test('Toggle collapse popover and claim.', async ({ page, context }) => {
42-
await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'react-router' });
43-
});
44-
45-
test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({
31+
test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({
4632
page,
47-
context,
4833
}) => {
49-
await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl });
50-
});
51-
52-
test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => {
53-
await testKeylessRemovedAfterEnvAndRestart({ page, context, app });
34+
const response = await page.goto(`${app.serverUrl}/`);
35+
expect(response?.status()).toBe(500);
36+
const content = await page.content();
37+
expect(content).toContain('no secret key provided');
38+
expect(content).toContain('npx clerk@latest init');
5439
});
5540
});

packages/astro/src/env.d.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ interface ImportMeta {
3131
declare namespace App {
3232
interface Locals {
3333
runtime?: { env: InternalEnv };
34-
keylessClaimUrl?: string;
35-
keylessApiKeysUrl?: string;
36-
keylessPublishableKey?: string;
3734
}
3835
}
3936

packages/astro/src/internal/create-clerk-instance.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { Ui } from '@clerk/ui/internal';
99

1010
import { $clerkStore } from '../stores/external';
1111
import { $clerk, $csrState } from '../stores/internal';
12-
import type { AstroClerkCreateInstanceParams, AstroClerkUpdateOptions, InternalRuntimeOptions } from '../types';
12+
import type { AstroClerkCreateInstanceParams, AstroClerkUpdateOptions } from '../types';
1313
import { invokeClerkAstroJSFunctions } from './invoke-clerk-astro-js-functions';
1414
import { mountAllClerkAstroJSComponents } from './mount-clerk-astro-js-components';
1515
import { runOnce } from './run-once';
@@ -54,18 +54,12 @@ async function createClerkInstanceInternal<TUi extends Ui = Ui>(options?: AstroC
5454
$clerk.set(clerkJSInstance);
5555
}
5656

57-
const internalOptions = options as AstroClerkCreateInstanceParams<TUi> & InternalRuntimeOptions;
58-
const keylessClaimUrl = internalOptions.__internal_keylessClaimUrl;
59-
const keylessApiKeysUrl = internalOptions.__internal_keylessApiKeysUrl;
60-
6157
const clerkOptions = {
6258
routerPush: createNavigationHandler(window.history.pushState.bind(window.history)),
6359
routerReplace: createNavigationHandler(window.history.replaceState.bind(window.history)),
6460
...options,
6561
// Pass the clerk-ui constructor promise to clerk.load()
6662
ui: { ...options?.ui, ClerkUI },
67-
...(keylessClaimUrl && { __internal_keyless_claimKeylessApplicationUrl: keylessClaimUrl }),
68-
...(keylessApiKeysUrl && { __internal_keyless_copyInstanceKeysUrl: keylessApiKeysUrl }),
6963
} as unknown as ClerkOptions;
7064

7165
initOptions = clerkOptions;

packages/astro/src/internal/merge-env-vars-with-params.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ const mergeEnvVarsWithParams = (
5454
isSatellite: paramSatellite || import.meta.env.PUBLIC_CLERK_IS_SATELLITE,
5555
proxyUrl: paramProxy || import.meta.env.PUBLIC_CLERK_PROXY_URL,
5656
domain: paramDomain || import.meta.env.PUBLIC_CLERK_DOMAIN,
57-
// In keyless mode, use server-injected publishableKey from params
5857
publishableKey:
5958
paramPublishableKey || internalOptions?.publishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '',
6059
__internal_clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL,
@@ -69,10 +68,6 @@ const mergeEnvVarsWithParams = (
6968
unsafe_disableDevelopmentModeConsoleWarning:
7069
paramUnsafeDisableDevelopmentModeConsoleWarning ??
7170
isTruthy(import.meta.env.PUBLIC_CLERK_UNSAFE_DISABLE_DEVELOPMENT_MODE_CONSOLE_WARNING),
72-
// Read from params (server-injected via __CLERK_ASTRO_SAFE_VARS__)
73-
// These are dynamically resolved by middleware, not from env vars
74-
__internal_keylessClaimUrl: internalOptions?.keylessClaimUrl,
75-
__internal_keylessApiKeysUrl: internalOptions?.keylessApiKeysUrl,
7671
...rest,
7772
};
7873
};

packages/astro/src/server/__tests__/get-safe-env.test.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,6 @@ describe('getSafeEnv', () => {
211211
expect(env.pk).toBeUndefined();
212212
expect(env.sk).toBeUndefined();
213213
});
214-
215-
it('prefers keylessPublishableKey over all env sources', () => {
216-
process.env.PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_from_process';
217-
218-
const locals = createLocals({
219-
runtime: { env: undefined as unknown as InternalEnv },
220-
keylessPublishableKey: 'pk_keyless',
221-
});
222-
const env = getSafeEnv(locals);
223-
224-
expect(env.pk).toBe('pk_keyless');
225-
226-
delete process.env.PUBLIC_CLERK_PUBLISHABLE_KEY;
227-
});
228214
});
229215

230216
describe('getClientSafeEnv', () => {

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

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@ import type { APIContext } from 'astro';
2727

2828
import { authAsyncStorage } from '#async-local-storage';
2929

30-
import { canUseKeyless } from '../utils/feature-flags';
3130
import { buildClerkHotloadScript } from './build-clerk-hotload-script';
3231
import { clerkClient } from './clerk-client';
3332
import { createCurrentUser } from './current-user';
3433
import { getClientSafeEnv, getSafeEnv, initCloudflareEnv } from './get-safe-env';
35-
import { resolveKeysWithKeylessFallback } from './keyless/utils';
3634
import { serverRedirectWithAuth } from './server-redirect-with-auth';
3735
import type {
3836
AstroMiddleware,
@@ -87,42 +85,9 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]): any => {
8785
const patchedRequest = patchRequest(context.request);
8886
const clerkRequest = createClerkRequest(patchedRequest);
8987

90-
// Resolve keyless URLs per-request in development
91-
let keylessClaimUrl: string | undefined;
92-
let keylessApiKeysUrl: string | undefined;
93-
let keylessOptions = options;
94-
95-
if (canUseKeyless) {
96-
try {
97-
const env = getSafeEnv(context);
98-
const configuredPublishableKey = options?.publishableKey || env.pk;
99-
const configuredSecretKey = options?.secretKey || env.sk;
100-
101-
const keylessResult = await resolveKeysWithKeylessFallback(
102-
configuredPublishableKey,
103-
configuredSecretKey,
104-
context,
105-
);
106-
107-
keylessClaimUrl = keylessResult.claimUrl;
108-
keylessApiKeysUrl = keylessResult.apiKeysUrl;
109-
110-
// Override keys with keyless values if returned
111-
if (keylessResult.publishableKey || keylessResult.secretKey) {
112-
keylessOptions = {
113-
...options,
114-
...(keylessResult.publishableKey && { publishableKey: keylessResult.publishableKey }),
115-
...(keylessResult.secretKey && { secretKey: keylessResult.secretKey }),
116-
};
117-
}
118-
} catch {
119-
// Silently fail - continue without keyless
120-
}
121-
}
122-
12388
const requestState = await clerkClient(context).authenticateRequest(
12489
clerkRequest,
125-
createAuthenticateRequestOptions(clerkRequest, keylessOptions, context),
90+
createAuthenticateRequestOptions(clerkRequest, options, context),
12691
);
12792

12893
const locationHeader = requestState.headers.get(constants.Headers.Location);
@@ -145,16 +110,6 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]): any => {
145110

146111
decorateAstroLocal(clerkRequest, authObjectFn, context, requestState);
147112

148-
// Store keyless data for injection into client
149-
if (keylessClaimUrl || keylessApiKeysUrl) {
150-
context.locals.keylessClaimUrl = keylessClaimUrl;
151-
context.locals.keylessApiKeysUrl = keylessApiKeysUrl;
152-
// Also store the resolved publishable key so client can use it
153-
if (keylessOptions?.publishableKey) {
154-
context.locals.keylessPublishableKey = keylessOptions.publishableKey;
155-
}
156-
}
157-
158113
/**
159114
* ALS is crucial for guaranteeing SSR in UI frameworks like React.
160115
* This currently powers the `useAuth()` React hook and any other hook or Component that depends on it.

0 commit comments

Comments
 (0)