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
5 changes: 5 additions & 0 deletions .changeset/cli-error-copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/shared': patch
---

Update missing and invalid key error messages to recommend the Clerk CLI: `npx clerk@latest init` to create an application, `npx clerk@latest env pull` to fetch the keys of an existing one, and `npx clerk@latest deploy` to provision a production instance. The Dashboard link is kept for manual key copying.
5 changes: 5 additions & 0 deletions .changeset/nextjs-cli-key-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/nextjs': patch
---

`clerkMiddleware()` now points key misconfiguration at the Clerk CLI. Missing keys throw an error recommending `npx clerk@latest init`, with `npx clerk@latest deploy` / `npx clerk@latest env pull --instance prod` guidance for production (`code=missing_env_keys`). The publishable key format is also validated upfront (`code=invalid_env_keys`) instead of failing later with `Publishable key not valid.` The messages are the same in development and production.
4 changes: 2 additions & 2 deletions packages/backend/src/__tests__/createRedirect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('redirect(redirectAdapter)', () => {
} as any);

expect(() => redirectToSignIn({ returnBackUrl })).toThrowError(
'@clerk/backend: Missing publishableKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.',
'@clerk/backend: Missing publishableKey. To set up Clerk for this project, run:\n\n npx clerk@latest init',
);
});
});
Expand Down Expand Up @@ -258,7 +258,7 @@ describe('redirect(redirectAdapter)', () => {
});

expect(() => redirectToSignUp({ returnBackUrl })).toThrowError(
'@clerk/backend: Missing publishableKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.',
'@clerk/backend: Missing publishableKey. To set up Clerk for this project, run:\n\n npx clerk@latest init',
);
});

Expand Down
6 changes: 3 additions & 3 deletions packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ describe('clerkMiddleware(params)', () => {
it('propagates middleware dynamic keys to the next request', async () => {
const options = {
secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx',
publishableKey: 'pk_test_xxxxxxxxxxxxx',
publishableKey: 'pk_test_ZHluYW1pYy1rZXlzLmNsZXJrLmFjY291bnRzLmRldiQ',
signInUrl: '/foo',
signUpUrl: '/bar',
};
Expand All @@ -286,7 +286,7 @@ describe('clerkMiddleware(params)', () => {
it('with synchronous callback', async () => {
const options = {
secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx',
publishableKey: 'pk_test_xxxxxxxxxxxxx',
publishableKey: 'pk_test_ZHluYW1pYy1rZXlzLmNsZXJrLmFjY291bnRzLmRldiQ',
signInUrl: '/foo',
signUpUrl: '/bar',
};
Expand All @@ -313,7 +313,7 @@ describe('clerkMiddleware(params)', () => {
it('with asynchronous callback', async () => {
const options = {
secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx',
publishableKey: 'pk_test_xxxxxxxxxxxxx',
publishableKey: 'pk_test_ZHluYW1pYy1rZXlzLmNsZXJrLmFjY291bnRzLmRldiQ',
signInUrl: '/foo',
signUpUrl: '/bar',
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { automatedEnvironmentVariables } from '@clerk/shared/utils';
import type { NextFetchEvent } from 'next/server';
import { NextRequest } from 'next/server';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

// The mock SHOULD exist before the imports: keys are present but not parseable as Clerk keys, so
// the invalid-key error path is reachable.
vi.mock(import('../constants.js'), async importOriginal => {
const actual = await importOriginal();
return {
...actual,
PUBLISHABLE_KEY: 'pk_test_placeholder',
SECRET_KEY: 'sk_test_placeholder',
};
});

describe('clerkMiddleware when Clerk env vars are invalid', () => {
beforeEach(() => {
vi.stubEnv('NODE_ENV', 'development');
automatedEnvironmentVariables.forEach(name => {
vi.stubEnv(name, undefined);
vi.stubGlobal(name, undefined);
});
vi.resetModules();
});

afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();
vi.resetModules();
});

const runMiddleware = async () => {
const { clerkMiddleware } = await import('../clerkMiddleware.js');
const request = new NextRequest('https://example.com/protected');
return clerkMiddleware()(request, {} as NextFetchEvent);
};

it('throws the invalid-key error pointing at the CLI', async () => {
await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest init/);
await expect(runMiddleware()).rejects.toThrow(/\(code=invalid_env_keys\)/);
});

it('throws the same error regardless of NODE_ENV', async () => {
vi.stubEnv('NODE_ENV', 'production');
await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest init/);
await expect(runMiddleware()).rejects.toThrow(/\(code=invalid_env_keys\)/);
});

it('names the env var, the expected key format, and all three CLI commands', async () => {
const { invalidEnvKeys } = await import('../errors.js');
expect(invalidEnvKeys).toContain('NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY');
expect(invalidEnvKeys).toContain('pk_test_');
expect(invalidEnvKeys).toContain('npx clerk@latest init');
expect(invalidEnvKeys).toContain('npx clerk@latest env pull');
});
});
16 changes: 11 additions & 5 deletions packages/nextjs/src/server/clerkMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { canUseKeyless } from '../utils/feature-flags';
import { clerkClient } from './clerkClient';
import { DOMAIN, PROXY_URL, PUBLISHABLE_KEY, SECRET_KEY, SIGN_IN_URL, SIGN_UP_URL } from './constants';
import { type ContentSecurityPolicyOptions, createContentSecurityPolicyHeaders } from './content-security-policy';
import { errorThrower } from './errorThrower';
import { invalidEnvKeys, missingEnvVars } from './errors';
import { getHeader } from './headers-utils';
import { getKeylessCookieValue } from './keyless';
import { clerkMiddlewareRequestDataStorage, clerkMiddlewareRequestDataStore } from './middleware-storage';
Expand Down Expand Up @@ -155,12 +155,18 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl

const publishableKey = assertKey(
resolvedParams.publishableKey || PUBLISHABLE_KEY || keyless?.publishableKey,
() => errorThrower.throwMissingPublishableKeyError(),
() => {
throw new Error(missingEnvVars);
},
);

const secretKey = assertKey(resolvedParams.secretKey || SECRET_KEY || keyless?.secretKey, () =>
errorThrower.throwMissingSecretKeyError(),
);
const secretKey = assertKey(resolvedParams.secretKey || SECRET_KEY || keyless?.secretKey, () => {
throw new Error(missingEnvVars);
});

if (!parsePublishableKey(publishableKey)) {
throw new Error(invalidEnvKeys);
}

// Handle Frontend API proxy requests early, before authentication
const requestUrl = new URL(request.nextUrl.href);
Expand Down
16 changes: 16 additions & 0 deletions packages/nextjs/src/server/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ For more details, see https://clerk.com/err/auth-middleware
`;
};

export const missingEnvVars = `Clerk: Missing environment variables (NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY).

To set up Clerk for this project, run:

npx clerk@latest init

The command is non-interactive and requires no Clerk account or login: it provisions temporary development keys automatically and writes them to your .env.local file. Then restart your dev server. If you already have a Clerk application, run \`npx clerk@latest env pull\` to write its keys, or copy them from https://dashboard.clerk.com into .env.local. Deploying to production? Run \`npx clerk@latest deploy\` to provision a production instance, or \`npx clerk@latest env pull --instance prod\` to use an existing one, and set the keys in your deployment environment. (code=missing_env_keys)`;

export const invalidEnvKeys = `Clerk: Invalid environment keys. The publishable key (NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY or the publishableKey option) is set but is not a valid Clerk key (expected format: pk_test_... or pk_live_...).

To set up Clerk for this project with valid keys, run:

npx clerk@latest init

The command is non-interactive and requires no Clerk account or login: it provisions temporary development keys automatically and writes them to your .env.local file. Then restart your dev server. If you already have a Clerk application, run \`npx clerk@latest env pull\` to write its keys (\`--instance prod\` for production keys), or copy them from https://dashboard.clerk.com into .env.local. (code=invalid_env_keys)`;

export const authSignatureInvalid = `Clerk: Unable to verify request, this usually means the Clerk middleware did not run. Ensure Clerk's middleware is properly integrated and matches the current route. For more information, see: https://clerk.com/docs/reference/nextjs/clerk-middleware. (code=auth_signature_invalid)`;

export const encryptionKeyInvalid = `Clerk: Unable to decrypt request data, this usually means the encryption key is invalid. Ensure the encryption key is properly set. For more information, see: https://clerk.com/docs/reference/nextjs/clerk-middleware#dynamic-keys. (code=encryption_key_invalid)`;
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/src/__tests__/error.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ describe('ErrorThrower', () => {

it('throws the correct error message and interpolates pkg and known parameters', () => {
expect(() => errorThrower.throwInvalidPublishableKeyError({ key: 'whatever' })).toThrow(
'@clerk/test-package: The publishableKey passed to Clerk is invalid. You can get your Publishable key at https://dashboard.clerk.com/last-active?path=api-keys. (key=whatever)',
'@clerk/test-package: The publishableKey passed to Clerk is invalid (key=whatever, expected format: pk_test_... or pk_live_...). To create a Clerk application with valid keys, run:\n\n npx clerk@latest init',
);
});

it('throws the correct error message and interpolates pkg if no parameters are provided', () => {
expect(() => errorThrower.throwMissingPublishableKeyError()).toThrow(
'@clerk/test-package: Missing publishableKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.',
'@clerk/test-package: Missing publishableKey. To set up Clerk for this project, run:\n\n npx clerk@latest init',
);
});

Expand Down
4 changes: 2 additions & 2 deletions packages/shared/src/__tests__/loadClerkJsScript.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('loadClerkJsScript(options)', () => {

test('throws error when publishableKey is missing', async () => {
await expect(loadClerkJsScript({} as any)).rejects.toThrow(
'@clerk/react: Missing publishableKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.',
'@clerk/react: Missing publishableKey. To set up Clerk for this project, run:\n\n npx clerk@latest init',
);
});

Expand Down Expand Up @@ -310,7 +310,7 @@ describe('loadClerkUIScript(options)', () => {

test('throws error when publishableKey is missing', async () => {
await expect(loadClerkUIScript({} as any)).rejects.toThrow(
'@clerk/react: Missing publishableKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.',
'@clerk/react: Missing publishableKey. To set up Clerk for this project, run:\n\n npx clerk@latest init',
);
});

Expand Down
18 changes: 15 additions & 3 deletions packages/shared/src/errors/errorThrower.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
const DefaultMessages = Object.freeze({
InvalidProxyUrlErrorMessage: `The proxyUrl passed to Clerk is invalid. The expected value for proxyUrl is an absolute URL or a relative path with a leading '/'. (key={{url}})`,
InvalidPublishableKeyErrorMessage: `The publishableKey passed to Clerk is invalid. You can get your Publishable key at https://dashboard.clerk.com/last-active?path=api-keys. (key={{key}})`,
MissingPublishableKeyErrorMessage: `Missing publishableKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.`,
MissingSecretKeyErrorMessage: `Missing secretKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.`,
InvalidPublishableKeyErrorMessage: `The publishableKey passed to Clerk is invalid (key={{key}}, expected format: pk_test_... or pk_live_...). To create a Clerk application with valid keys, run:

npx clerk@latest init

The command is non-interactive and requires no Clerk account or login. If you already have a Clerk application, run \`npx clerk@latest env pull\` to write its keys to your env file (\`--instance prod\` for production keys), or copy its Publishable key from https://dashboard.clerk.com/last-active?path=api-keys.`,
MissingPublishableKeyErrorMessage: `Missing publishableKey. To set up Clerk for this project, run:

npx clerk@latest init

The command is non-interactive and requires no Clerk account or login: it creates a Clerk application and writes the required keys to your env file. If you already have a Clerk application, run \`npx clerk@latest env pull\` to write its keys, or copy them from https://dashboard.clerk.com/last-active?path=api-keys. Deploying to production? Run \`npx clerk@latest deploy\` to provision a production instance, or \`npx clerk@latest env pull --instance prod\` to use an existing one.`,
MissingSecretKeyErrorMessage: `Missing secretKey. To set up Clerk for this project, run:

npx clerk@latest init

The command is non-interactive and requires no Clerk account or login: it creates a Clerk application and writes the required keys to your env file. If you already have a Clerk application, run \`npx clerk@latest env pull\` to write its keys, or copy them from https://dashboard.clerk.com/last-active?path=api-keys. Deploying to production? Run \`npx clerk@latest deploy\` to provision a production instance, or \`npx clerk@latest env pull --instance prod\` to use an existing one.`,
MissingClerkProvider: `{{source}} can only be used within the <ClerkProvider /> component. Learn more: https://clerk.com/docs/components/clerk-provider`,
});

Expand Down
Loading