Claude Code for Clerk Auth — Workflow Guide

Written by Michael Lip · Solo founder of Zovo · $400K+ on Upwork · 100% JSS Join 50+ builders · More at zovo.one

The Setup

You are adding authentication, user management, and organization support to a Next.js application using Clerk. Clerk provides pre-built UI components, session management, and webhook-driven user sync. Claude Code can implement auth flows and protected routes, but it defaults to building custom auth from scratch or using NextAuth patterns that do not apply to Clerk.

What Claude Code Gets Wrong By Default

  1. Builds custom login forms with bcrypt. Claude creates email/password forms, hashes passwords, and manages sessions manually. Clerk provides <SignIn /> and <SignUp /> components that handle the entire auth UI including OAuth, MFA, and email verification.

  2. Uses NextAuth middleware patterns. Claude writes withAuth() wrappers or getServerSession() calls. Clerk uses its own middleware via clerkMiddleware() in middleware.ts and auth() helper for server-side session access.

  3. Stores user data in a local database on signup. Claude creates a users table and inserts records during registration. Clerk manages user data in its own service — sync to your database using Clerk webhooks (user.created, user.updated events), not during the auth flow.

  4. Ignores Clerk’s organization features. Claude builds team/org management from scratch. Clerk has built-in organization support with roles, invitations, and <OrganizationSwitcher /> components.

The CLAUDE.md Configuration

# Clerk Auth Next.js Project

## Architecture
- Auth: Clerk (@clerk/nextjs)
- Framework: Next.js 14 App Router
- Middleware: clerkMiddleware() in middleware.ts
- User sync: Clerk webhooks to local database

## Clerk Rules
- Use <SignIn />, <SignUp /> components — never build custom auth forms
- Protect routes in middleware.ts with clerkMiddleware()
- Access user in server components: auth() from @clerk/nextjs/server
- Access user in client components: useUser() from @clerk/nextjs
- Webhook sync: /api/webhooks/clerk endpoint handles user.created events
- Verify webhook signatures with svix package
- Environment: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY

## Conventions
- ClerkProvider wraps app in layout.tsx
- Public routes listed in middleware.ts matcher config
- User DB sync happens via webhook only, never in auth flow
- Use clerk.users.getUser(userId) for server-side user lookup
- Organization ID from auth().orgId in multi-tenant routes
- Never store passwords — Clerk handles all credential management

Workflow Example

You want to add protected API routes and user-specific data. Prompt Claude Code:

“Protect the /api/projects endpoint so only authenticated users can access it. Return projects belonging to the current user. Add a webhook handler to sync new Clerk users to our database.”

Claude Code should use auth() from @clerk/nextjs/server in the API route to get userId, query projects filtered by that ID, and create a /api/webhooks/clerk endpoint that verifies the Svix signature and inserts new users on the user.created event.

Common Pitfalls

  1. Missing the <ClerkProvider> wrapper. Claude adds Clerk hooks in components but forgets the provider in layout.tsx. All Clerk hooks (useUser, useAuth, useOrganization) fail silently without the provider wrapping the component tree.

  2. Webhook signature verification skipped. Claude processes webhook payloads without verifying the Svix signature. This allows anyone to fake webhook events. Always verify using the svix package with your webhook signing secret.

  3. Using currentUser() in client components. Claude calls the server-only currentUser() function in client components, causing build errors. Use useUser() hook in client components and currentUser() or auth() only in server components and API routes.