Claude
Skills
Sign in
Back

clerk-auth

Included with Lifetime
$97 forever

This skill provides comprehensive knowledge for integrating Clerk authentication in React, Next.js, and Cloudflare Workers applications. It should be used when setting up user authentication, implementing protected routes, verifying JWT tokens, creating custom JWT templates with user metadata and organization claims, configuring Clerk middleware, integrating with shadcn/ui components, testing authentication flows, or troubleshooting Clerk authentication errors. Use when: adding Clerk to React/Vite projects, setting up Clerk in Next.js App Router, implementing Clerk authentication in Cloudflare Workers, configuring clerkMiddleware for route protection, creating custom JWT templates with shortcodes (user.id, user.email, user.public_metadata.role), accessing session claims for RBAC, integrating with Supabase/Grafbase, verifying tokens with @clerk/backend, integrating Clerk with Hono, using Clerk shadcn/ui components, writing E2E tests with Playwright, generating test session tokens, using test email addresses and phone numbers, or encountering authentication errors. Prevents 11 documented issues: missing secret key errors, API key migration failures, JWKS cache race conditions, CSRF vulnerabilities from missing authorizedParties, import path errors after Core 2 upgrade, JWT size limit issues, deprecated API version warnings, ClerkProvider JSX component errors, async auth() helper confusion, environment variable misconfiguration, and Vite dev mode 431 header errors. Keywords: clerk, clerk auth, clerk authentication, @clerk/nextjs, @clerk/backend, @clerk/clerk-react, clerkMiddleware, createRouteMatcher, verifyToken, useUser, useAuth, useClerk, JWT template, JWT claims, JWT shortcodes, custom JWT, session claims, getToken template, user.public_metadata, org_id, org_slug, org_role, CustomJwtSessionClaims, sessionClaims metadata, clerk webhook, clerk secret key, clerk publishable key, protected routes, Cloudflare Workers auth, Next.js auth, shadcn/ui auth, @hono/clerk-auth, "Missing Clerk Secret Key", "cannot be used as a JSX component", JWKS error, authorizedParties, clerk middleware, ClerkProvider, UserButton, SignIn, SignUp, clerk testing, test emails, test phone numbers, +clerk_test, 424242 OTP, session token, testing token, @clerk/testing, playwright testing, E2E testing, clerk test mode, bot detection, generate session token, test users

Design

What this skill does


# Clerk Authentication

**Status**: Production Ready ✅
**Last Updated**: 2025-10-28
**Dependencies**: None
**Latest Versions**: @clerk/[email protected], @clerk/[email protected], @clerk/[email protected], @clerk/[email protected]

---

## Quick Start (10 Minutes)

Choose your framework:
- [React (Vite)](#react-vite-setup) - ClerkProvider + hooks
- [Next.js App Router](#nextjs-app-router-setup) - Middleware + async auth()
- [Cloudflare Workers](#cloudflare-workers-setup) - Backend verification

---

## React (Vite) Setup

### 1. Install Clerk

\`\`\`bash
npm install @clerk/clerk-react
\`\`\`

**Latest Version**: @clerk/[email protected] (verified 2025-10-22)

### 2. Configure ClerkProvider

Update \`src/main.tsx\`:

\`\`\`typescript
import React from 'react'
import ReactDOM from 'react-dom/client'
import { ClerkProvider } from '@clerk/clerk-react'
import App from './App.tsx'
import './index.css'

// Get publishable key from environment
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

if (!PUBLISHABLE_KEY) {
  throw new Error('Missing Publishable Key')
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ClerkProvider publishableKey={PUBLISHABLE_KEY}>
      <App />
    </ClerkProvider>
  </React.StrictMode>,
)
\`\`\`

**CRITICAL**:
- Use \`VITE_\` prefix for environment variables in Vite
- ClerkProvider must wrap your entire app
- Source: https://clerk.com/docs/references/react/clerk-provider

### 3. Add Environment Variables

Create \`.env.local\`:

\`\`\`bash
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
\`\`\`

**Security Note**: Only \`VITE_\` prefixed vars are exposed to client code.

### 4. Use Authentication Hooks

\`\`\`typescript
import { useUser, useAuth, useClerk } from '@clerk/clerk-react'

function App() {
  // Get user object (includes email, metadata, etc.)
  const { isLoaded, isSignedIn, user } = useUser()

  // Get auth state and session methods
  const { userId, sessionId, getToken } = useAuth()

  // Get Clerk instance for advanced operations
  const { openSignIn, signOut } = useClerk()

  // Always check isLoaded before rendering auth-dependent UI
  if (!isLoaded) {
    return <div>Loading...</div>
  }

  if (!isSignedIn) {
    return <button onClick={() => openSignIn()}>Sign In</button>
  }

  return (
    <div>
      <h1>Welcome {user.firstName}!</h1>
      <p>Email: {user.primaryEmailAddress?.emailAddress}</p>
      <button onClick={() => signOut()}>Sign Out</button>
    </div>
  )
}
\`\`\`

**Why This Matters**:
- \`isLoaded\` prevents flash of wrong content
- \`useUser()\` provides full user object with metadata
- \`useAuth()\` provides session tokens and auth state
- Source: https://clerk.com/docs/references/react/use-user

---

## Next.js App Router Setup

### 1. Install Clerk

\`\`\`bash
npm install @clerk/nextjs
\`\`\`

**Latest Version**: @clerk/[email protected] (verified 2025-10-22)
- **New in v6**: Async auth() helper, Next.js 15 support, static rendering by default
- Source: https://clerk.com/changelog/2024-10-22-clerk-nextjs-v6

### 2. Configure Environment Variables

Create \`.env.local\`:

\`\`\`bash
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# Optional: Customize sign-in/up pages
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
\`\`\`

**CRITICAL**:
- \`CLERK_SECRET_KEY\` must NEVER be exposed to client
- Use \`NEXT_PUBLIC_\` prefix for client-side vars only
- Source: https://clerk.com/docs/guides/development/clerk-environment-variables

### 3. Add Middleware for Route Protection

Create \`middleware.ts\` in project root:

\`\`\`typescript
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

// Define which routes are public (everything else requires auth)
const isPublicRoute = createRouteMatcher([
  '/',
  '/sign-in(.*)',
  '/sign-up(.*)',
  '/api/webhooks(.*)', // Clerk webhooks should be public
])

export default clerkMiddleware(async (auth, request) => {
  // Protect all routes except public ones
  if (!isPublicRoute(request)) {
    await auth.protect()
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and static files
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}
\`\`\`

**CRITICAL**:
- \`auth.protect()\` is async in v6 (breaking change from v5)
- \`createRouteMatcher()\` accepts glob patterns
- Alternative: protect specific routes instead of inverting logic
- Source: https://clerk.com/docs/reference/nextjs/clerk-middleware

### 4. Wrap App with ClerkProvider

Update \`app/layout.tsx\`:

\`\`\`typescript
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
\`\`\`

### 5. Use auth() in Server Components

\`\`\`typescript
import { auth, currentUser } from '@clerk/nextjs/server'

export default async function DashboardPage() {
  // Get auth state (lightweight)
  const { userId, sessionId } = await auth()

  // Get full user object (heavier, fewer calls)
  const user = await currentUser()

  if (!userId) {
    return <div>Unauthorized</div>
  }

  return (
    <div>
      <h1>Dashboard</h1>
      <p>User ID: {userId}</p>
      <p>Email: {user?.primaryEmailAddress?.emailAddress}</p>
    </div>
  )
}
\`\`\`

**CRITICAL**:
- \`auth()\` is async in v6 (breaking change)
- Use \`auth()\` for lightweight checks
- Use \`currentUser()\` when you need full user object

---

## Cloudflare Workers Setup

### 1. Install Dependencies

\`\`\`bash
npm install @clerk/backend hono
\`\`\`

**Latest Versions**:
- @clerk/[email protected] (verified 2025-10-22)
- [email protected]

### 2. Configure Environment Variables

Create \`.dev.vars\` for local development:

\`\`\`bash
CLERK_SECRET_KEY=sk_test_...
CLERK_PUBLISHABLE_KEY=pk_test_...
\`\`\`

**Production**: Use \`wrangler secret put CLERK_SECRET_KEY\`

### 3. Implement Token Verification

Create \`src/index.ts\`:

\`\`\`typescript
import { Hono } from 'hono'
import { verifyToken } from '@clerk/backend'

type Bindings = {
  CLERK_SECRET_KEY: string
  CLERK_PUBLISHABLE_KEY: string
}

type Variables = {
  userId: string | null
  sessionClaims: any | null
}

const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()

// Middleware: Verify Clerk token
app.use('/api/*', async (c, next) => {
  const authHeader = c.req.header('Authorization')

  if (!authHeader) {
    c.set('userId', null)
    c.set('sessionClaims', null)
    return next()
  }

  const token = authHeader.replace('Bearer ', '')

  try {
    const { data, error } = await verifyToken(token, {
      secretKey: c.env.CLERK_SECRET_KEY,
      // IMPORTANT: Set authorizedParties to prevent CSRF attacks
      authorizedParties: ['https://yourdomain.com'],
    })

    if (error) {
      console.error('Token verification failed:', error)
      c.set('userId', null)
      c.set('sessionClaims', null)
    } else {
      c.set('userId', data.sub)
      c.set('sessionClaims', data)
    }
  } catch (err) {
    console.error('Token verification error:', err)
    c.set('userId', null)
    c.set('sessionClaims', null)
  }

  return next()
})

// Protected route
app.get('/api/protected', (c) => {
  const userId = c.get('userId')

  if (!userId) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  return c.json({
    message: 'This is protected',
    userId,
    sessionClaims: c.get('sessionClaims'),
  })
})

export default app
\`\`\`

**CRITICAL**:
- Always set \`authorizedParties\` to prevent CSRF attacks
- Use \`secretKey\`, not deprecated \`apiKey\`
- Source: https://clerk.com/docs/reference/backend/verify-token

---

## JWT Te
Files: 4
Size: 72.0 KB
Complexity: 49/100
Category: Design

Related in Design