@clerk/nextjs
>=5.0.0 <7.0.0postconditions28functions20last verified2026-06-23coverage score100%Postconditions: what we check
- auth · missing-clerk-middlewareerrorWhenauth() called without clerkMiddleware() configured in src/middleware.tsThrows
Error: Clerk: auth() was called but Clerk can't detect usage of clerkMiddleware()Required handlingMUST have clerkMiddleware() exported from src/middleware.ts with proper matcher configuration. Middleware must run for all routes where auth() is called.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - auth · auth-null-not-checkederrorWhenauth() result used without checking if userId existsThrows
Runtime error when accessing properties of null/undefined userRequired handlingMUST check auth().userId or auth().isAuthenticated before using authentication data. Pattern: `const { userId } = await auth(); if (!userId) return unauthorized();`costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - currentUser · current-user-not-cachedwarningWhencurrentUser() called multiple times in same request (Next.js 15+)Throws
Rate limit exceeded: HTTP 422 (100 req/10s per IP)Required handlingSHOULD wrap currentUser() with React cache() to avoid multiple API calls per request. Pattern: `const getCachedUser = cache(async () => await currentUser());`costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - currentUser · current-user-null-not-handlederrorWhencurrentUser() result used without null checkThrows
Runtime error accessing properties of nullRequired handlingMUST handle null case when currentUser() is called. Returns null if user not authenticated.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - clerkMiddleware · middleware-not-exportederrorWhenclerkMiddleware defined but not exported from middleware.tsThrows
Middleware never runs, auth() calls failRequired handlingMUST export clerkMiddleware() as default export from src/middleware.ts. Pattern: `export default clerkMiddleware();`costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - clerkMiddleware · middleware-matcher-missingwarningWhenclerkMiddleware without matcher configurationThrows
Middleware runs on static assets, causes 404 auth errorsRequired handlingSHOULD configure matcher to exclude static files and API routes that don't need auth. Pattern: `export const config = { matcher: ['/((?!.*\\..*|_next).*)', '/'] };`costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - protect · protect-not-in-try-catchwarningWhenauth.protect() called without try-catch in route handlerThrows
Uncaught error causes 500 instead of proper 401/404Required handlingSHOULD wrap auth.protect() in try-catch to handle authentication errors gracefully. Returns 404 for unauthenticated requests with session token, 401 for machine tokens.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[6] - create · signin-create-no-error-handlingerrorWhensignIn.create() called without try-catchThrows
ClerkAPIError with error codes (user_locked, form_param_missing, etc.)Required handlingMUST wrap signIn.create() in try-catch and handle ClerkAPIError. Check error.errors[0].code for: user_locked, password_required, form_param_missing. For user_locked, display lockout_expires_in_seconds from error.meta.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - create · user-lockout-meta-not-displayedwarningWhenuser_locked error caught but lockout_expires_in_seconds not shown to userThrows
Poor UX - user doesn't know when to retryRequired handlingSHOULD extract and display lockout_expires_in_seconds from error.meta when code is user_locked. Helps users know when account will unlock.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[7] - create · signup-create-no-error-handlingerrorWhensignUp.create() called without try-catchThrows
ClerkAPIError with error codes (form_password_length_too_short, already_signed_in, etc.)Required handlingMUST wrap signUp.create() in try-catch and handle ClerkAPIError. Common errors: form_password_length_too_short, form_param_missing, requires_captcha.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - verify · webhook-signature-not-verifiederrorWhenClerk webhook endpoint processes payload without signature verificationThrows
Security vulnerability - accepts forged webhook eventsRequired handlingMUST verify webhook signature using Webhook.verify() from svix library. Use CLERK_WEBHOOK_SECRET environment variable. Pattern: `const wh = new Webhook(secret); const event = wh.verify(payload, headers);`costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[8] - verify · webhook-verify-not-in-try-catchwarningWhenWebhook.verify() called without try-catchThrows
Unhandled error on invalid signature causes 500 instead of 400Required handlingSHOULD wrap Webhook.verify() in try-catch to return proper 400 response on verification failure.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[8] - getToken · get-token-null-not-handlederrorWhengetToken() result used without null checkThrows
Runtime error when user not authenticated (returns null)Required handlingMUST handle null case when getToken() is called. Returns null if user not authenticated or token invalid.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - setActive · set-active-no-error-handlingwarningWhensetActive() called without try-catchThrows
Error when session ID is invalid or session already activeRequired handlingSHOULD wrap setActive() in try-catch to handle invalid session errors. Can fail if session is null, invalid, or race conditions occur.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[10] - useSignIn · use-signin-no-error-statewarningWhenuseSignIn() used but errors not displayed to userThrows
Silent failures - users don't know why sign-in failedRequired handlingSHOULD maintain error state and display ClerkAPIError messages to users. Use isClerkAPIResponseError() to type-check errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - useClerk · use-clerk-outside-providerwarningWhenuseClerk() called outside ClerkProviderThrows
Error: useClerk must be used within ClerkProviderRequired handlingMUST ensure component using useClerk() is wrapped in ClerkProvider. In Next.js App Router, ClerkProvider should wrap root layout. NOTE: This is a likely false positive in Next.js App Router apps where ClerkProvider is in the root layout — static analysis cannot trace the component hierarchy. Verify manually that ClerkProvider is absent before treating as a real violation.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[10] - verifyWebhook · verify-webhook-no-error-handlingerrorWhenverifyWebhook() called without try-catchThrows
Throws Error if CLERK_WEBHOOK_SIGNING_SECRET is not set, if svix headers are missing, or if the webhook signature is invalid. All three failure modes produce thrown errors.Required handlingMUST wrap verifyWebhook() in try-catch. On failure, return HTTP 400. Pattern: try { const evt = await verifyWebhook(req); } catch(err) { return new Response('Error', { status: 400 }); }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[11] - verifyWebhook · verify-webhook-missing-env-varerrorWhenverifyWebhook() used but CLERK_WEBHOOK_SIGNING_SECRET env var not configuredThrows
Throws Error: "Missing webhook signing secret. Set the CLERK_WEBHOOK_SIGNING_SECRET environment variable with the webhook secret from the Clerk Dashboard." Note: the variable is CLERK_WEBHOOK_SIGNING_SECRET, NOT CLERK_WEBHOOK_SECRET (a common misconfiguration from older Clerk docs).Required handlingMUST set CLERK_WEBHOOK_SIGNING_SECRET in environment variables. Get the signing secret from Clerk Dashboard → Webhooks → Endpoint → Signing Secret. The value starts with "whsec_". Note: different from CLERK_SECRET_KEY.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[11] - createUser · create-user-no-error-handlingerrorWhen(await clerkClient()).users.createUser() called without try-catchThrows
Throws ClerkAPIResponseError when: - email already exists (HTTP 422, code: form_identifier_exists) - rate limit exceeded (HTTP 429, 100/10s dev, 1000/10s prod) - required fields are missing (HTTP 400) - authentication fails (HTTP 401/403) Access error details via: error.errors[0].code, error.errors[0].messageRequired handlingMUST wrap in try-catch and handle ClerkAPIResponseError. Use isClerkAPIResponseError(error) from '@clerk/nextjs/errors' to type-guard. For duplicate email: check error.errors[0].code === 'form_identifier_exists'. For rate limits (429): implement retry with exponential backoff.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - deleteUser · delete-user-no-error-handlingerrorWhen(await clerkClient()).users.deleteUser() called without try-catchThrows
Throws ClerkAPIResponseError when: - user not found (HTTP 404) - userId is null/undefined (requireId() throws synchronously before HTTP call) - request fails (HTTP 400/401/429)Required handlingMUST wrap in try-catch. Deletion is irreversible — unhandled errors may leave partial state (e.g., database records exist but Clerk user deleted, or vice versa). Always delete from Clerk first, then from your database. If database deletion fails, you must manually re-create or alert — there is no rollback for Clerk deletion.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[13] - banUser · ban-user-no-error-handlingerrorWhen(await clerkClient()).users.banUser() called without try-catchThrows
Throws ClerkAPIResponseError when: - user not found (HTTP 404) - user is already banned (HTTP 422) - auth fails (HTTP 401/403)Required handlingMUST wrap in try-catch. A failed ban in an abuse-response flow means the malicious user remains active and can continue unauthorized actions. Log and alert on any ban failure. Do not silently swallow errors. Use isClerkAPIResponseError(error) to distinguish API failures from network timeouts.costhighin prodimmediate exceptionusers seesecurity breachvisibilityvisibleSources[14] - attemptFirstFactor · attempt-first-factor-no-error-handlingerrorWhensignIn.attemptFirstFactor() called without try-catchThrows
Throws ClerkAPIResponseError with codes: - form_password_incorrect: wrong password entered (HTTP 422) - user_locked: account locked after max failed attempts (HTTP 403), error.errors[0].meta.lockout_expires_in_seconds contains seconds until unlock - strategy_for_user_invalid: this auth strategy is not enabled for the user - verification_failed: code is wrong or expired (for OTP strategies)Required handlingMUST wrap in try-catch. Show specific error messages to users. For user_locked: display lockout_expires_in_seconds from error.errors[0].meta. For form_password_incorrect: show "Invalid credentials" (avoid confirming username existence). Use isClerkAPIResponseError(error) to type-check before accessing error.errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - useUser · use-user-no-loaded-checkwarningWhenuseUser() result accessed before checking isLoadedThrows
TypeError: Cannot read properties of undefined when accessing user.firstName, user.emailAddresses, etc. before isLoaded is true. user is undefined (not null) during Clerk's initial hydration phase.Required handlingMUST check isLoaded before accessing user properties. Pattern: const { user, isLoaded } = useUser(); if (!isLoaded) return <Skeleton />; Alternative: use <SignedIn> and <SignedOut> guard components which handle loading.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - clerkFrontendApiProxy · proxy-missing-publishable-keyerrorWhenclerkFrontendApiProxy() called without publishableKey in options or CLERK_PUBLISHABLE_KEY env varThrows
Returns HTTP 500 Response with JSON body: { errors: [{ code: "proxy_configuration_error", message: "Missing publishableKey..." }] } Does NOT throw — returns a 500 error Response that is silently proxied to the client.Required handlingMUST ensure CLERK_PUBLISHABLE_KEY environment variable is set, or pass publishableKey in the options parameter. Without this, ALL Clerk frontend requests through the proxy will fail with 500 errors. Users will see a broken authentication UI with no clear error message since the 500 is returned as a JSON response, not a thrown exception.costhighin prodsilent failureusers seeservice unavailablevisibilitysilentSources[19] - clerkFrontendApiProxy · proxy-missing-secret-keyerrorWhenclerkFrontendApiProxy() called without secretKey in options or CLERK_SECRET_KEY env varThrows
Returns HTTP 500 Response with JSON body: { errors: [{ code: "proxy_configuration_error", message: "Missing secretKey..." }] } The Clerk-Secret-Key header is required for all proxied requests.Required handlingMUST ensure CLERK_SECRET_KEY environment variable is set. This is the same secret key used for other Clerk server-side operations. Without it, the proxy returns 500 for every request. Note: this key is server-side only and must NOT be prefixed with NEXT_PUBLIC_ — it is sent as the Clerk-Secret-Key header to Clerk's FAPI.costhighin prodsilent failureusers seeservice unavailablevisibilitysilentSources[19] - clerkFrontendApiProxy · proxy-network-failure-returns-502warningWhenclerkFrontendApiProxy() encounters a network error when forwarding to Clerk FAPIThrows
Returns HTTP 502 Response with JSON body: { errors: [{ code: "proxy_request_failed", message: "Failed to proxy request to Clerk FAPI: <error>" }] } Network errors include DNS resolution failure, connection timeout, TLS handshake failure, or Clerk FAPI being down.Required handlingSHOULD monitor proxy route responses for 502 status codes. The proxy silently converts network errors to 502 responses — the calling code will not throw. If your proxy route is the only path for Clerk authentication, a 502 means ALL user auth is broken. Consider adding health check monitoring for the proxy endpoint.costmediumin prodsilent failureusers seedegraded performancevisibilitysilentSources[19] - verifyToken · verify-token-no-error-handlingerrorWhenverifyToken() called without try-catch or .catch() handlerThrows
Throws TokenVerificationError when: - reason="token-expired": JWT exp claim is in the past (HTTP 401 expected) - reason="token-invalid": malformed JWT or wrong header type (HTTP 400) - reason="token-invalid-signature": signature does not match JWKS public key (HTTP 401) - reason="token-invalid-algorithm": JWT alg header is not RS256 or other accepted algorithm - reason="token-invalid-authorized-parties": azp claim missing from authorizedParties list - reason="token-not-active-yet": JWT nbf claim is in the future (clock skew) - reason="token-iat-in-the-future": JWT iat claim is in the future (clock skew) - reason="token-verification-failed": general verification failure (e.g. aud mismatch) - reason="secret-key-invalid": CLERK_SECRET_KEY env var missing or malformed - reason="jwk-remote-failed-to-load": Clerk JWKS endpoint unreachable (network failure) - reason="jwk-remote-missing" / "jwk-kid-mismatch": JWKS missing key for token's kid header Access: error.reason (one of TokenVerificationErrorReason values), error.action (one of TokenVerificationErrorAction values), error.message.Required handlingMUST wrap verifyToken() in try-catch. The dominant case (expired or missing tokens) is a normal unauthenticated request and should return HTTP 401, not 500. Pattern: try { const payload = await verifyToken(token, { secretKey: env.CLERK_SECRET_KEY }); // use payload.sub for userId } catch (err) { if (err instanceof TokenVerificationError) { return new Response('Unauthorized', { status: 401 }); } throw err; // re-throw unexpected errors } For JWKS network failures (reason: jwk-remote-failed-to-load), implement a retry with exponential backoff — a transient Clerk outage should not 500 the route.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - useReverification · use-reverification-cancel-not-handledwarningWhenuseReverification() wrapped fetcher called without handling isReverificationCancelledErrorThrows
The fetcher returned by useReverification() rejects with ClerkRuntimeError where err.code === "reverification_cancelled" when the user closes the reverification modal without completing it. Use isReverificationCancelledError(err) from '@clerk/nextjs/errors' to type-check. Distinct from network or API errors that may also reject from the underlying fetcher.Required handlingSHOULD catch and distinguish reverification cancellation from real errors. Pattern: const [enhancedFetcher] = useReverification(myFetcher); try { const result = await enhancedFetcher(); // success path } catch (err) { if (isReverificationCancelledError(err)) { // user cancelled — show retry button, not error toast return; } // genuine error — show error UI showError(err); } Without this distinction, a user who accidentally closes the modal sees a confusing error toast when nothing actually broke. The action also did not execute, so optimistic UI must be reverted.costlowin prodimmediate exceptionusers seedegraded performancevisibilityvisible
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [1]clerk.com/docs/reference/nextjsClerk Middleware
- [2]clerk.com/docs/reference/nextjsAuth
- [4]clerk.com/docs/reference/nextjsCurrent User
- [5]clerk.com/docs/guides/developmentError Handling
- [6]clerk.com/docs/reference/nextjsAuth
- [7]clerk.com/docs/guides/developmentFrontend Api
- [8]clerk.com/docs/guides/developmentOverview
- [9]clerk.com/docs/reference/nextjsGet Token
- [10]clerk.com/docs/reference/clerk-reactUse Clerk
- [11]clerk.com/docs/webhooks/sync-dataSync Data
- [12]clerk.com/docs/references/backendCreate User
- [13]clerk.com/docs/references/backendDelete User
- [14]clerk.com/docs/references/backendBan User
- [15]clerk.com/docs/custom-flows/error-handlingError Handling
- [16]clerk.com/docs/custom-flows/email-passwordEmail Password
- [17]clerk.com/docs/references/clerk-reactUse User
- [18]clerk.com/docs/references/nextjsOverview
- [19]clerk.com/docs/advanced-usage/using-proxiesUsing Proxies
- [20]clerk.com/docs/references/backendVerify Token
- [22]clerk.com/docs/guides/secureReverification
- [23]clerk.com/docs/references/reactUse Reverification
- [21]github.com/clerk/javascript/blobclerk/javascript · verify.ts
- [3]github.com/clerk/javascript/issuesclerk/javascript issue #4894
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
@clerk/nextjs Nark profile - Sources
Package: @clerk/nextjs (Clerk authentication SDK for Next.js) Version Range: >=5.0.0 <7.0.0 Last Verified: 2026-02-24 Research Thread: dev-notes/contexts/0010-clerk-nextjs-research.md
Official Documentation
-
Clerk Error Handling Guide https://clerk.com/docs/guides/development/custom-flows/error-handling Comprehensive guide to handling authentication errors in custom flows
-
Frontend API Error Codes https://clerk.com/docs/guides/development/errors/frontend-api Complete list of 200+ error codes with descriptions and error objects
-
clerkMiddleware Reference https://clerk.com/docs/reference/nextjs/clerk-middleware Middleware configuration and usage patterns for Next.js
-
auth() API Reference https://clerk.com/docs/reference/nextjs/app-router/auth Server-side authentication state retrieval in App Router
-
currentUser() Reference https://clerk.com/docs/reference/nextjs/current-user Backend user data fetching with API implications
-
Session Management & Token Refresh https://clerk.com/docs/guides/sessions/force-token-refresh Token lifecycle, automatic refresh (60s), and force refresh patterns
-
Webhooks Overview https://clerk.com/docs/guides/development/webhooks/overview Webhook signature verification and event handling
-
getToken() Reference https://clerk.com/docs/reference/nextjs/get-token Token retrieval with custom claims and skipCache option
-
useClerk() Hook Reference https://clerk.com/docs/reference/clerk-react/use-clerk Client-side Clerk context access and setActive() usage
GitHub Issues (Real-World Error Patterns)
-
Issue #4894: Rate Limiting with Next.js 15 https://github.com/clerk/javascript/issues/4894 Critical: Multiple currentUser() calls cause HTTP 422 rate limit errors Root cause: Next.js 15 changed fetch() caching defaults Impact: Shared IP addresses (Vercel) hit 100 req/10s limit Solution: Wrap currentUser() with React cache()
-
Issue #4989: Session Encryption Errors https://github.com/clerk/javascript/issues/4989 Token decryption failures with error "Session encryption is not configured" Affects custom JWT claims and token validation
-
Issue #1418: Infinite Redirect Loop https://github.com/clerk/javascript/issues/1418 Middleware configuration errors cause redirect loops Occurs when auth() called but clerkMiddleware() not properly set up
-
Issue #1616: Token Refresh Failures https://github.com/clerk/javascript/issues/1616 Automatic token refresh (60s interval) fails under load Causes mid-session authentication failures
-
Issue #XXXX: Asset 404 Loop Pattern documented in error handling guide Non-existent assets trigger 404 page without middleware auth() fails because clerkMiddleware() never runs on 404 route
Security Advisories
- GHSA-9mp4-77wg-rwx9: Webhook Signature Verification https://github.com/clerk/javascript/security/advisories/GHSA-9mp4-77wg-rwx9 Security advisory emphasizing webhook signature verification requirement Unverified webhooks allow attackers to forge user events
Real-World Usage Analysis
-
Precedent (SaaS Boilerplate) File:
test-repos/precedent/Patterns observed: Middleware setup, auth() usage in route handlers Found: Proper error handling in sign-in forms -
Next-js-Boilerplate File:
test-repos/Next-js-Boilerplate/Patterns observed: ClerkProvider setup, useUser() hook usage Found: currentUser() called multiple times without caching -
Cal.com (Scheduling Platform) File:
test-repos/cal.com/Patterns observed: Webhook integration, auth.protect() usage Found: Webhook signature verification implemented -
Dub (Link Management) File:
test-repos/dub/Patterns observed: Middleware matcher configuration Found: Good middleware setup with proper matchers -
Documenso (Document Signing) File:
test-repos/documenso/Patterns observed: Session management, token refresh Found: Manual token refresh on API failures -
Taxonomy (Next.js Starter) File:
test-repos/taxonomy/Patterns observed: Basic auth() integration Found: Missing error handling in some API routes
Key Behavioral Insights
Critical Finding 1: The Middleware Dependency
@clerk/nextjs has a hard dependency on clerkMiddleware() for auth() to work:
// ❌ WRONG - auth() without middleware
// In app/api/users/route.ts
import { auth } from '@clerk/nextjs/server';
export async function GET() {
const { userId } = await auth(); // ERROR: auth() called but clerkMiddleware not detected
}
// ✅ REQUIRED - middleware.ts must exist
// In src/middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/'],
};
Source: https://clerk.com/docs/reference/nextjs/clerk-middleware
Critical Finding 2: Rate Limit Storm (Next.js 15)
Next.js 15 removed automatic fetch() caching, causing multiple API calls:
// ❌ WRONG - Hits rate limit (100 req/10s per IP)
export default async function Layout() {
const user = await currentUser(); // Call #1
return (
<Navbar user={await currentUser()} /> // Call #2
<Sidebar user={await currentUser()} /> // Call #3
);
}
// ✅ CORRECT - Cache wrapper prevents multiple calls
import { cache } from 'react';
import { currentUser } from '@clerk/nextjs/server';
const getCachedUser = cache(async () => await currentUser());
export default async function Layout() {
const user = await getCachedUser(); // Only 1 API call
return (
<Navbar user={user} />
<Sidebar user={user} />
);
}
Source: https://github.com/clerk/javascript/issues/4894
Critical Finding 3: Webhook Security Requirement
Clerk webhooks MUST be verified to prevent forged events:
// ❌ CRITICAL SECURITY FLAW - No signature verification
export async function POST(req: Request) {
const event = await req.json();
// Process event directly - ALLOWS FORGERY
await db.updateUser(event.data.id);
}
// ✅ SECURE - Signature verification required
import { Webhook } from 'svix';
export async function POST(req: Request) {
const payload = await req.text();
const headers = {
'svix-id': req.headers.get('svix-id'),
'svix-timestamp': req.headers.get('svix-timestamp'),
'svix-signature': req.headers.get('svix-signature'),
};
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
try {
const event = wh.verify(payload, headers);
await db.updateUser(event.data.id); // Safe to process
} catch (err) {
return Response.json({ error: 'Invalid signature' }, { status: 400 });
}
}
Source: https://github.com/clerk/javascript/security/advisories/GHSA-9mp4-77wg-rwx9
High-Priority Finding: Account Lockout UX
User lockout errors include retry timing metadata:
// ❌ POOR UX - User doesn't know when to retry
catch (err) {
if (isClerkAPIResponseError(err)) {
if (err.errors[0]?.code === 'user_locked') {
setError('Account locked. Try again later.');
}
}
}
// ✅ GOOD UX - Show specific unlock time
catch (err) {
if (isClerkAPIResponseError(err)) {
const error = err.errors[0];
if (error?.code === 'user_locked') {
const expiresIn = error.meta?.lockout_expires_in_seconds || 900;
const unlockTime = new Date(Date.now() + expiresIn * 1000);
setError(`Account locked until ${unlockTime.toLocaleTimeString()}`);
}
}
}
Source: https://clerk.com/docs/guides/development/errors/frontend-api#user_locked
Implementation Status
Contract Version: 1.0.0 Last Verified: 2026-02-24 Analyzer Support: Partial (6/19 postconditions working)
Working Postconditions (6)
Null Check Detection (Phase 7.1):
- ✅
auth-null-not-checked- Detects missing null checks after auth() - ✅
current-user-null-not-handled- Detects missing null checks after currentUser() - ✅
get-token-null-not-handled- Detects missing null checks after getToken()
File System Inspection (Phase 7.2):
- ✅
middleware-not-exported- Validates middleware.ts exists and exports clerkMiddleware - ✅
middleware-matcher-missing- Checks for config.matcher export - ✅
missing-clerk-middleware- Detects auth() usage without middleware setup
Validation Results:
- Tested against: precedent, Next-js-Boilerplate
- True Positive Rate: 100%
- False Positive Rate: 0%
Deferred Postconditions (13)
Require Complex Analysis:
webhook-signature-not-verified- Requires webhook pattern detectionwebhook-verify-not-in-try-catch- Requires webhook pattern detectioncurrent-user-not-cached- Requires multi-call tracking across fileuse-clerk-outside-provider- Requires React component tree analysisuser-lockout-meta-not-displayed- Requires semantic error message analysis
Likely Working (Need Testing):
signin-create-no-error-handling- Generic try-catch detectionsignup-create-no-error-handling- Generic try-catch detectionprotect-not-in-try-catch- Generic try-catch detectionset-active-no-error-handling- Generic try-catch detectionuse-signin-no-error-state- Client-side error state detection
Future Enhancements Needed:
- Multi-call detection within same file/component
- Webhook signature verification pattern matching
- React component tree analysis for provider wrapping
- Error message content analysis
Analyzer Enhancements Delivered
Phase 7.1: Null Check Detection
- Handles destructuring:
const { userId } = await auth() - Handles direct assignment:
const user = await currentUser() - Detects compound conditions:
if (!isAuthenticated || !userId) - Supports optional chaining:
user?.emailAddresses - Recursive AST traversal for || and && operators
Phase 7.2: File System Inspection
checkFileExists()- Search root/, src/, app/ directoriescheckFileImportsAndExports()- AST-based import/export validationcheckClerkMiddlewareExists()- Middleware configuration check- Broadly applicable to 40-60% of future packages
Error Categories
Authentication & Authorization (P0 - Critical)
authentication_invalid- Invalid credentialsauthorization_invalid- Insufficient permissionssession_reverification_required- Session needs re-auth- Missing middleware error - auth() without clerkMiddleware()
Session Management (P0 - Critical)
token_expired- JWT expired (60s default)token_refresh_failed- Auto-refresh mechanism failed- Rate limit exceeded - HTTP 422 (100 req/10s)
- Token verification failures with custom
audclaims
Sign In/Up Flows (P1 - High)
user_locked- Account lockout after max attempts (HTTP 403)- Includes
lockout_expires_in_secondsin meta
- Includes
user_banned- Permanently blockedpassword_required- Missing passwordform_param_missing- Required field missingform_password_length_too_short- Validation failed
OAuth & SSO (P1 - High)
oauth_config_missing- Provider not configuredoauth_account_already_connected- Duplicate OAuth connectionoauth_provider_not_enabled- Provider disabled
Webhooks (P0 - Critical)
- Signature mismatch - Tampered payload
- Payload verification failures
- Missing CLERK_WEBHOOK_SECRET
Rate Limiting (P0 - Critical)
- HTTP 429 - Rate limit exceeded
action_blocked- Fraud detectionrequires_captcha- CAPTCHA verification needed
Testing Strategy
Fixtures validate:
- Middleware dependency (auth() without clerkMiddleware)
- Null handling (auth(), currentUser(), getToken())
- Error handling (signIn.create(), signUp.create())
- Webhook signature verification
- Rate limiting (multiple currentUser() calls)
- Error state display (useSignIn, useSignUp)
Real-world validation against:
- precedent (6 repos total)
- Next-js-Boilerplate
- cal.com
- dub
- documenso
- taxonomy
Related Packages
- @clerk/clerk-react - React-only Clerk package
- @auth0/nextjs-auth0 - Alternative auth provider (separate contract)
- next-auth - Open-source auth library (separate contract)
- svix - Webhook signature verification (used by Clerk)
Research completed: 2026-02-24 Total sources: 21 references GitHub issues analyzed: 5 Real-world repos: 6 Confidence level: HIGH