home / skills / phrazzld / claude-config / clerk-auth

clerk-auth skill

/skills/clerk-auth

This skill helps you implement Clerk authentication with Next.js and Convex, enabling secure user sessions, webhooks, and protected routes.

npx playbooks add skill phrazzld/claude-config --skill clerk-auth

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
5.3 KB
---
name: clerk-auth
description: "Clerk authentication integration patterns for Next.js and Convex. Invoke for: user authentication, session management, JWT templates, webhook handling, middleware configuration, protected routes, Convex auth integration."
effort: high
---

# Clerk Authentication Patterns

Authentication integration patterns for Clerk with Next.js and Convex backends.

## Core Concepts

### Authentication Flow
1. User authenticates via Clerk (sign-in/sign-up)
2. Clerk issues session token (JWT)
3. Frontend passes token to backend
4. 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

```typescript
// 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

```bash
# .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`:

```json
{
  "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

```typescript
// convex/auth.config.ts
export default {
  providers: [
    {
      domain: process.env.CLERK_JWT_ISSUER_DOMAIN,
      applicationID: "convex",
    },
  ],
}
```

### Backend User Identity

```typescript
// 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:
- URL: `https://your-app.com/api/webhooks/clerk`
- Events: `user.created`, `user.updated`, `user.deleted`

### Webhook Handler

```typescript
// 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
1. Check JWT template name matches config
2. Verify `CLERK_JWT_ISSUER_DOMAIN` is set
3. Ensure middleware isn't blocking auth routes

### Webhook failures
1. Verify `CLERK_WEBHOOK_SECRET` is set (not just locally)
2. Check webhook URL uses canonical domain (no redirects)
3. Ensure `/api/webhooks/*` is in public routes

### Session not persisting
1. Check cookies are being set (dev tools)
2. Verify domain configuration in Clerk Dashboard
3. 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

Overview

This skill provides practical Clerk authentication integration patterns for Next.js frontends and Convex backends. It covers middleware configuration, JWT templates, webhook handling, session management, and Convex auth wiring to verify user identity. Use it to implement secure protected routes and reliable user sync between Clerk and your backend.

How this skill works

Middleware enforces route protection and lets public webhook and auth pages remain accessible. Frontend obtains Clerk session tokens and sends them to backend endpoints, where Convex or server routes verify JWTs and extract claims. Webhooks from Clerk are verified with svix signatures and used to synchronize user records server-side.

When to use it

  • Protect Next.js routes and API endpoints with Clerk middleware
  • Integrate Convex backend auth to map Clerk users to application identities
  • Issue and verify custom JWTs for backend authorization
  • Handle Clerk webhooks to sync users and react to account lifecycle events
  • Troubleshoot authentication or session persistence issues in dev and prod

Best practices

  • Declare public webhook routes in middleware so webhooks are not blocked
  • Keep JWT templates minimal — include only necessary claims (sub, email, name)
  • Store Clerk user ID as a foreign key in your database for reliable mapping
  • Verify webhook signatures with svix and require CLERK_WEBHOOK_SECRET in production
  • Set Clerk env vars on both local and hosting platform to avoid silent failures

Example use cases

  • Protect a Next.js dashboard so only authenticated users can view it via clerkMiddleware
  • Create a Convex getCurrentUser query that uses ctx.auth.getUserIdentity() to return JWT claims
  • Configure a Clerk JWT template named exactly 'convex' and match it in convex/auth.config.ts
  • Receive user.created events on /api/webhooks/clerk to create user records in your DB
  • Debug an "Unauthenticated" error by verifying JWT template name, issuer domain, and middleware rules

FAQ

What env vars are required?

Set NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, and CLERK_JWT_ISSUER_DOMAIN; also set NEXT_PUBLIC_CLERK_SIGN_IN_URL and SIGN_UP_URL if used.

Why are webhooks failing?

Common causes: missing CLERK_WEBHOOK_SECRET, webhook URL redirects, or webhook route blocked by middleware. Verify svix headers and the canonical URL.

How do I map Clerk users to my DB?

Sync users via webhooks and store the Clerk user ID as a foreign key; avoid on-demand lookups for large-scale apps.