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

clerk-auth skill

This skill helps you implement secure Clerk authentication with Next.js and Convex, automating token verification, webhook handling, 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."
---

# 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 Clerk authentication integration patterns for Next.js frontends and Convex backends. It documents middleware configuration, JWT templates, webhook handling, and server-side identity extraction to make authentication reliable and secure. Use it to implement session management, protected routes, and Convex auth verification with minimal friction.

How this skill works

The skill describes the full flow: Clerk issues a session JWT after user sign-in, the frontend forwards that token to backend endpoints, and the backend verifies the token and extracts user identity. It explains Next.js middleware to protect routes and make webhooks public, how to create a Clerk JWT template named for Convex, and how to use Convex's ctx.auth.getUserIdentity() to access JWT claims. It also includes a signed webhook handler pattern using Svix verification.

When to use it

  • You need secure session management and protected routes in a Next.js app.
  • You want to integrate Clerk-authenticated users with a Convex backend.
  • You must process user lifecycle events reliably via webhooks.
  • You need server-side access to minimal user claims (id, email, name).
  • You want guidance on environment variable and middleware setup for production.

Best practices

  • Expose webhook endpoints as public routes and verify signatures with a webhook secret.
  • Keep JWT templates minimal—include only claims your backend needs (sub, email, name).
  • Store Clerk user ID as a foreign key in your database for reliable user mapping.
  • Set Clerk environment variables consistently in both local and production environments.
  • Sync users via webhooks rather than on-demand queries to reduce race conditions and latency.

Example use cases

  • Protecting Next.js API routes and pages with clerkMiddleware to enforce authentication.
  • Issuing a 'convex' JWT template in Clerk and configuring Convex to trust that issuer.
  • Handling user.created and user.deleted events to sync or clean up database records.
  • Using ctx.auth.getUserIdentity() in Convex queries to authorize actions based on user claims.
  • Verifying Svix-signed webhooks in a Next.js API route to prevent spoofed events.

FAQ

What causes unexpected 'Unauthenticated' errors?

Common causes are mismatched JWT template names, missing CLERK_JWT_ISSUER_DOMAIN, or middleware blocking auth routes; verify template name, env vars, and middleware matcher.

Why are webhook deliveries failing?

Ensure CLERK_WEBHOOK_SECRET is set in the deployed environment, the webhook URL uses a canonical domain with no redirects, and the route is marked public in middleware.