Clerk Authentication Patterns
Authentication integration patterns for Clerk with Next.js and Convex backends.
Core Concepts
Authentication Flow
-
User authenticates via Clerk (sign-in/sign-up)
-
Clerk issues session token (JWT)
-
Frontend passes token to backend
-
Backend verifies token and extracts user identity
Key Components
-
Clerk Dashboard: User management, JWT templates, webhooks
-
@clerk/nextjs: React hooks, middleware, components
-
Convex auth: ctx.auth.getUserIdentity() for backend verification
Next.js Setup
Middleware
// middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher([ '/', '/sign-in(.)', '/sign-up(.)', '/api/webhooks(.*)', // Webhooks must be public ])
export default clerkMiddleware((auth, req) => { if (!isPublicRoute(req)) { auth().protect() } })
export const config = { matcher: ['/((?!.\..|_next).)', '/', '/(api|trpc)(.)'], }
Environment Variables
.env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
Critical: Set on BOTH Vercel and local. Mismatch causes silent failures.
Convex Integration
JWT Template (Clerk Dashboard)
Create JWT template named convex :
{ "sub": "{{user.id}}", "iss": "https://clerk.your-domain.com", "email": "{{user.primary_email_address}}", "name": "{{user.full_name}}" }
Template name is case-sensitive. Must match convex/auth.config.ts .
Convex Auth Config
// convex/auth.config.ts export default { providers: [ { domain: process.env.CLERK_JWT_ISSUER_DOMAIN, applicationID: "convex", }, ], }
Backend User Identity
// convex/users.ts import { query, mutation } from "./_generated/server"
export const getCurrentUser = query({ handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity() if (!identity) return null
// identity contains JWT claims:
// - subject (Clerk user ID)
// - email
// - name
return identity
}, })
Webhook Handling
Webhook URL
Configure in Clerk Dashboard → Webhooks:
-
Events: user.created , user.updated , user.deleted
Webhook Handler
// app/api/webhooks/clerk/route.ts import { Webhook } from 'svix' import { headers } from 'next/headers' import { WebhookEvent } from '@clerk/nextjs/server'
export async function POST(req: Request) { const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET
if (!WEBHOOK_SECRET) { throw new Error('Missing CLERK_WEBHOOK_SECRET') }
const headerPayload = headers() const svix_id = headerPayload.get('svix-id') const svix_timestamp = headerPayload.get('svix-timestamp') const svix_signature = headerPayload.get('svix-signature')
if (!svix_id || !svix_timestamp || !svix_signature) { return new Response('Missing svix headers', { status: 400 }) }
const payload = await req.json() const body = JSON.stringify(payload)
const wh = new Webhook(WEBHOOK_SECRET) let evt: WebhookEvent
try { evt = wh.verify(body, { 'svix-id': svix_id, 'svix-timestamp': svix_timestamp, 'svix-signature': svix_signature, }) as WebhookEvent } catch (err) { console.error('Webhook verification failed:', err) return new Response('Invalid signature', { status: 400 }) }
// Handle events switch (evt.type) { case 'user.created': // Sync user to database break case 'user.updated': // Update user in database break case 'user.deleted': // Handle user deletion break }
return new Response('OK', { status: 200 }) }
Common Issues
"Unauthenticated" errors
-
Check JWT template name matches config
-
Verify CLERK_JWT_ISSUER_DOMAIN is set
-
Ensure middleware isn't blocking auth routes
Webhook failures
-
Verify CLERK_WEBHOOK_SECRET is set (not just locally)
-
Check webhook URL uses canonical domain (no redirects)
-
Ensure /api/webhooks/* is in public routes
Session not persisting
-
Check cookies are being set (dev tools)
-
Verify domain configuration in Clerk Dashboard
-
Ensure HTTPS in production
Best Practices
-
Sync users via webhooks, not on-demand
-
Store Clerk ID as foreign key in your database
-
Use currentUser() for server components
-
Use useUser() for client components
-
Protect API routes with auth() in route handlers
-
Keep JWT templates minimal (only needed claims)
References
-
references/convex-integration.md — Detailed Convex auth patterns
-
references/webhook-events.md — Clerk webhook event types
-
references/troubleshooting.md — Common issues and solutions
Related Skills
-
billing-security — Payment and auth security patterns
-
external-integration-patterns — General external service integration
-
env-var-hygiene — Environment variable management