Learn-skills.dev api-auth-clerk
Clerk managed authentication - ClerkProvider, middleware, pre-built components, hooks, server-side auth, organizations, webhooks
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/api-auth-clerk" ~/.claude/skills/neversight-learn-skills-dev-api-auth-clerk && rm -rf "$T"
data/skills-md/agents-inc/skills/api-auth-clerk/SKILL.mdClerk Authentication Patterns
Quick Guide: Clerk provides managed authentication with pre-built UI components, server-side helpers, and organization-based multi-tenancy. Use
for route protection,clerkMiddleware()for conditional rendering, hooks for client state, and<Show>/auth()for server-side auth. Clerk Core 3 (2026) replacescurrentUser()/<SignedIn>with<SignedOut>, renames the middleware file to<Show>(Next.js 16+), and consolidates packages.proxy.ts
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use
for ALL server-side imports -- NEVER import server helpers from @clerk/nextjs/server
)@clerk/nextjs
(You MUST verify webhooks using Clerk's
helper -- NEVER trust unverified webhook payloads)verifyWebhook
(You MUST use
component instead of deprecated <Show>
/<SignedIn>
/<SignedOut>
-- these are removed in Core 3)<Protect>
(You MUST NOT pass the full
object to the client -- it contains currentUser()
that must stay server-side)privateMetadata
(You MUST protect routes in BOTH middleware AND data access layer -- middleware alone is insufficient)
</critical_requirements>
Auto-detection: Clerk, ClerkProvider, clerkMiddleware, @clerk/nextjs, useUser, useAuth, useClerk, useSession, useOrganization, SignIn, SignUp, UserButton, UserProfile, OrganizationSwitcher, auth(), currentUser(), CLERK_SECRET_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, Show when="signed-in"
When to use:
- Adding authentication and user management to an application
- Building multi-tenant B2B apps with organization-based access control
- Using pre-built sign-in/sign-up UI components with customizable theming
- Protecting routes with middleware and server-side authorization checks
- Syncing Clerk user data to your database via webhooks
Key patterns covered:
- ClerkProvider setup, environment variables, middleware configuration
- Pre-built UI components (
,<SignIn>
,<SignUp>
,<UserButton>
)<Show> - Client-side hooks (
,useUser
,useAuth
,useSession
)useOrganization - Server-side auth (
,auth()
) in Server Components, Route Handlers, Server ActionscurrentUser() - Middleware route protection with
andclerkMiddleware()createRouteMatcher() - Organization-based multi-tenancy with roles and permissions
- Webhook handling with Svix signature verification
When NOT to use:
- Self-hosted auth requirement (need full control over auth data storage)
- Cannot use a third-party auth service (compliance/regulatory constraints)
- Simple API key authentication (custom middleware is sufficient)
- Budget constraints prevent using a managed service
Detailed Resources:
- reference.md - Decision frameworks, hooks quick reference, Core 3 migration cheat sheet
- examples/core.md - ClerkProvider, environment variables, middleware configuration
- examples/components.md - Pre-built components, customization, appearance prop
- examples/hooks.md - useUser, useAuth, useSession, loading states, conditional rendering
- examples/server.md - Server Components, API routes, Server Actions, webhook handling
- examples/organizations.md - Organization management, roles, permissions, RBAC
<philosophy>
Philosophy
Clerk is a managed authentication platform that handles the entire auth lifecycle: sign-up, sign-in, session management, user profiles, organizations, and MFA. Instead of building auth from scratch, you integrate Clerk's SDK and pre-built components.
Core principles:
- Defense in depth -- Protect routes at the middleware layer AND verify auth at every data access point. Middleware alone is insufficient (CVE-2025-29927 demonstrated middleware bypass vulnerabilities).
- Server-first auth -- Use
andauth()
in Server Components and Route Handlers. Only use client hooks (currentUser()
,useUser
) when you need reactive client-side state.useAuth - Pre-built over custom -- Use Clerk's
,<SignIn>
,<SignUp>
components. Only build custom flows when the pre-built components genuinely cannot meet requirements.<UserButton> - Organizations for multi-tenancy -- Use Clerk Organizations with roles and permissions for B2B apps. Do not build custom tenant systems on top of Clerk's user model.
- Webhook-driven sync -- Sync Clerk data to your database via webhooks, not by polling. Always verify webhook signatures with
.verifyWebhook
When to use Clerk:
- You need auth quickly with minimal custom code
- You want pre-built UI components for sign-in/sign-up/user management
- You need organization-based multi-tenancy with RBAC
- You want managed MFA, SSO (SAML/OIDC), and social login
When NOT to use Clerk:
- You need full control over auth data storage (self-hosted requirement)
- You cannot use a third-party auth service (compliance/regulatory)
- Your app only needs simple API key authentication
- Budget constraints prevent using a managed service
<patterns>
Core Patterns
Pattern 1: ClerkProvider and Middleware Setup
Every Clerk app needs
<ClerkProvider> wrapping the app and clerkMiddleware() protecting routes. See examples/core.md for full setup examples.
Key rules:
goes insideClerkProvider
, not wrapping<body>
-- Core 3 requires this<html>- Use
env var, never hardcode keysNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - Middleware file is
on Next.js 16+ orproxy.ts
on Next.js <=15middleware.ts - Webhook endpoint must be in the public routes list (verified separately by
)verifyWebhook
is the env var for webhook signing (notCLERK_WEBHOOK_SIGNING_SECRET
)CLERK_WEBHOOK_SECRET
// proxy.ts (Next.js 16+) or middleware.ts (Next.js <=15) const isPublicRoute = createRouteMatcher([ "/", "/sign-in(.*)", "/sign-up(.*)", "/api/webhooks(.*)", ]); export default clerkMiddleware(async (auth, req) => { if (!isPublicRoute(req)) { await auth.protect(); } });
Why good: Public routes explicitly whitelisted, everything else requires auth, webhook endpoint is public (verified separately)
// BAD: clerkMiddleware() with no callback export default clerkMiddleware(); // All routes are PUBLIC by default!
Why bad:
clerkMiddleware() without callback attaches auth data but does not protect any route
Pattern 2: Pre-Built UI Components and <Show>
<Show>Use
<Show> for conditional rendering (Core 3 replacement for <SignedIn>/<SignedOut>/<Protect>). See examples/components.md for full examples.
<Show when="signed-out"><SignInButton /></Show> <Show when="signed-in"><UserButton /></Show> <Show when={{ role: "org:admin" }}><AdminPanel /></Show> <Show when={(has) => has({ permission: "org:invoices:manage" })}><InvoiceManager /></Show>
Why good:
<Show> is the Core 3 API, supports string/object/callback conditions, fallback prop for unauthorized
// BAD: Deprecated -- removed in Core 3 <SignedIn>...</SignedIn> <SignedOut>...</SignedOut> <Protect role="admin">...</Protect>
Why bad:
<SignedIn>, <SignedOut>, <Protect> removed in Core 3, use <Show> with when prop
Sign-in/sign-up pages require catch-all route segments
[[...sign-in]] for multi-step flows (MFA, OAuth callbacks).
Pattern 3: Client-Side Hooks
Use hooks in Client Components for reactive auth state. Always check
before accessing data. See examples/hooks.md for complete examples.isLoaded
"use client"; const { isLoaded, isSignedIn, user } = useUser(); if (!isLoaded) return <div>Loading...</div>; if (!isSignedIn) return <div>Please sign in</div>; // Now safe to access user.firstName, user.emailAddresses, etc.
Why good: Prevents hydration errors from accessing
undefined during init, prevents TypeError from accessing null when signed out
// BAD: Accessing user without guards const { user } = useUser(); return <p>{user.firstName}</p>; // Runtime error when isLoaded=false or isSignedIn=false
Why bad:
user is undefined until loaded and null when signed out
Key hooks:
-- user profile data (name, email, avatar)useUser()
-- session tokens (useAuth()
),getToken()
,userId
,orgId
for authorizationhas()
-- active org data, membership, paginated members listuseOrganization()
-- re-authenticate before sensitive actions (Core 3)useReverification()
In Core 3,
getToken() throws ClerkOfflineError when offline instead of returning null -- always wrap in try/catch.
Pattern 4: Server-Side Authentication
Use
auth() and currentUser() from @clerk/nextjs/server. See examples/server.md for complete examples.
-- lightweight, no API call, reads session claims:auth()
import { auth } from "@clerk/nextjs/server"; const { userId, orgId, isAuthenticated, redirectToSignIn } = await auth(); if (!isAuthenticated) return redirectToSignIn();
-- full user object, makes Backend API call:currentUser()
import { currentUser } from "@clerk/nextjs/server"; const user = await currentUser(); // CRITICAL: Pick safe fields before passing to client components const safeData = { firstName: user.firstName, imageUrl: user.imageUrl };
Why critical:
currentUser() returns privateMetadata that must never reach the client. Explicitly pick fields.
Defense in depth: Auth MUST be checked in every Server Action and Route Handler, not just middleware:
"use server"; const { userId } = await auth(); if (!userId) throw new Error("Unauthorized"); // userId scopes all queries/mutations to current user
Pattern 5: Authorization with Roles and Permissions
Use
auth.protect() or has() for granular access control. See examples/organizations.md for RBAC patterns.
// Middleware: role-based route protection await auth.protect((has) => has({ role: "org:admin" })); // Server Component: permission check const { has } = await auth(); if (!has({ permission: "org:invoices:delete" })) redirect("/dashboard"); // Client: conditional rendering by role <Show when={{ role: "org:admin" }}><AdminPanel /></Show>
Permission format:
org:<feature>:<action> (e.g., org:invoices:create, org:reports:read)
Default roles:
org:admin (all permissions), org:member (read-only). Custom roles configured in Clerk Dashboard.
Pattern 6: Webhook Handling
Use Clerk webhooks (via Svix) to sync user data to your database. See examples/server.md for complete handler.
import { verifyWebhook } from "@clerk/nextjs/webhooks"; export async function POST(req: Request) { const evt = await verifyWebhook(req); // reads CLERK_WEBHOOK_SIGNING_SECRET env var // evt.type: "user.created" | "user.updated" | "user.deleted" | "organization.*" | ... // evt.data: UserJSON | OrganizationJSON | etc. }
Why good:
verifyWebhook validates Svix signature, always returns 200 to prevent retries, webhook route is public in middleware (verified by signature not by auth)
// BAD: No signature verification const body = await req.json(); // Anyone can POST fake data! await db.insert(users).values(body.data); // Security vulnerability
Why bad: No signature verification means anyone can send fake webhook events to your endpoint
Import types from
(Core 3): @clerk/shared/types
UserJSON, OrganizationJSON, OrganizationMembershipJSON
</patterns>
<decision_framework>
Decision Framework
Client vs Server Auth
Where do you need auth data? |-- Server Component, Route Handler, Server Action | |-- Need just userId/sessionId? --> auth() | |-- Need full user object? --> currentUser() | |-- Need to protect the route? --> auth.protect() | +-- Need org context? --> auth() returns orgId, orgRole | +-- Client Component (interactive UI) |-- Need user profile data? --> useUser() |-- Need session/token data? --> useAuth() |-- Need org data? --> useOrganization() +-- Need low-level Clerk API? --> useClerk()
Route Protection Strategy
What kind of route is it? |-- Public (landing, sign-in, sign-up, webhooks) | +-- Add to isPublicRoute matcher, skip auth.protect() | |-- Authenticated (dashboard, profile, settings) | +-- auth.protect() in middleware + auth() check in data layer | +-- Authorized (admin, org-specific, permission-gated) |-- Role-based? --> auth.protect({ role: "org:admin" }) +-- Permission-based? --> auth.protect((has) => has({ permission: "org:feature:action" }))
Component Choice
What auth UI do you need? |-- Full sign-in page --> <SignIn /> on catch-all route |-- Full sign-up page --> <SignUp /> on catch-all route |-- Sign-in button (modal) --> <SignInButton /> |-- User avatar + menu --> <UserButton /> |-- Full profile editor --> <UserProfile /> |-- Org switcher --> <OrganizationSwitcher /> +-- Conditional content --> <Show when="signed-in"> or <Show when={{ role: "..." }}>
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Importing server helpers from
instead of@clerk/nextjs
(breaks in Server Components)@clerk/nextjs/server - Using deprecated
/<SignedIn>
/<SignedOut>
components (removed in Core 3)<Protect> - Passing full
object to client components (leakscurrentUser()
)privateMetadata - Trusting webhook payloads without
signature verificationverifyWebhook - Relying solely on middleware for route protection (middleware can be bypassed)
- Importing types from
instead of@clerk/types
(Core 3 rename)@clerk/shared/types
Medium Priority Issues:
- Not checking
before accessing hook data (causes hydration errors)isLoaded - Using
on the client side (it is server-only)currentUser() - Hardcoding Clerk keys instead of using environment variables
- Using
(deprecated, replaced byauthMiddleware()
)clerkMiddleware() - Not making webhook endpoint public in middleware matcher
- Using
instead ofCLERK_WEBHOOK_SECRETCLERK_WEBHOOK_SIGNING_SECRET
Common Mistakes:
- Naming middleware file
on Next.js 16+ (should bemiddleware.ts
) orproxy.ts
on Next.js <=15 (should beproxy.ts
)middleware.ts - Forgetting catch-all segments
on sign-in/sign-up pages (breaks multi-step flows)[[...sign-in]] - Using
without try/catch in Core 3 (throwsgetToken()
when offline instead of returning null)ClerkOfflineError - Not adding
toprefetch={false}
components pointing at protected routes from public pages<Link>
Gotchas & Edge Cases:
counts against Backend API rate limits -- prefercurrentUser()
hook on the client when possibleuseUser()
in Server Components is deduplicated per request (safe to call multiple times)auth()
requires an active organization in the session<Show when={{ role: "org:admin" }}>- Organization roles use the
prefix (e.g.,org:
,org:admin
,org:member
)org:billing - Clerk Core 3 requires Node.js 20.9.0+, Next.js 15.2.3+
renamed to@clerk/clerk-react
in Core 3 -- update imports after upgrade@clerk/react
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use
for ALL server-side imports -- NEVER import server helpers from @clerk/nextjs/server
)@clerk/nextjs
(You MUST verify webhooks using Clerk's
helper -- NEVER trust unverified webhook payloads)verifyWebhook
(You MUST use
component instead of deprecated <Show>
/<SignedIn>
/<SignedOut>
-- these are removed in Core 3)<Protect>
(You MUST NOT pass the full
object to the client -- it contains currentUser()
that must stay server-side)privateMetadata
(You MUST protect routes in BOTH middleware AND data access layer -- middleware alone is insufficient)
Failure to follow these rules will create authentication vulnerabilities or break on Clerk Core 3.
</critical_reminders>