Setup & Providers

Set up your Clerk account, configure API keys, add social logins, and enable webhook sync

This guide walks you through setting up Clerk authentication in your Kit project. By the end, you will have working sign-in, sign-up, social login, and automatic database synchronization.

Create a Clerk Account

1

Sign up at Clerk

Go to clerk.com and create a free account. Clerk's free tier is generous — it includes up to 10,000 monthly active users.
2

Create an application

In the Clerk Dashboard, click Create application. Choose a name for your app (e.g., "My SaaS") and select which sign-in methods you want to enable. You can change these later.
At minimum, enable Email address as an identifier. You can also enable Google and GitHub social logins right away — see the Social Login Providers section below.
3

Note your environment

Clerk creates a Development instance by default. This is perfect for local development. When you are ready to go live, you will create a separate Production instance in the Clerk Dashboard.

Get Your API Keys

After creating your application, Clerk shows you two API keys:
  • Publishable Key (pk_test_...) — Safe to expose in the browser. Used by ClerkProvider to initialize the SDK.
  • Secret Key (sk_test_...) — Server-only. Used for webhook verification and server-side API calls. Never expose this in client code.
You can find these anytime in the Clerk Dashboard under API Keys.

Configure Environment Variables

Add your Clerk keys to apps/boilerplate/.env.local. Kit uses 8 Clerk-related environment variables:
.env.example
# IMPORTANT: Replace these empty keys with your actual Clerk keys!
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
CLERK_WEBHOOK_SECRET=
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/login
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/register
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
Here is what each variable does:
VariableRequiredPurpose
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYYesInitializes the Clerk SDK in the browser
CLERK_SECRET_KEYYesServer-side operations and webhook verification
CLERK_WEBHOOK_SECRETYesVerifies webhook signatures (see Webhook Setup)
NEXT_PUBLIC_CLERK_SIGN_IN_URLNoSign-in page path. Default: /login
NEXT_PUBLIC_CLERK_SIGN_UP_URLNoSign-up page path. Default: /register
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URLNoRedirect after sign-in. Default: /dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URLNoRedirect after sign-up. Default: /dashboard
The URL variables have sensible defaults already configured in Kit. You typically only need to set the three key variables:
bash
# apps/boilerplate/.env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_key_here
CLERK_SECRET_KEY=sk_test_your_key_here
CLERK_WEBHOOK_SECRET=whsec_your_secret_here

ClerkProvider in Root Layout

Kit's root layout already includes the ClerkProvider. You do not need to add it yourself — it is pre-configured and environment-aware.
The provider uses the dynamic prop, which is critical for Next.js 14 with streaming:
typescript
<ClerkProvider
  dynamic
  appearance={{
    elements: {
      formButtonPrimary: 'bg-primary hover:bg-primary/90',
      footerActionLink: 'text-primary hover:text-primary/90',
    },
  }}
>
  {content}
</ClerkProvider>
The appearance object customizes Clerk's pre-built components (sign-in form, user button) to match your app's primary color. Since Kit uses CSS custom properties for theming, the bg-primary class automatically adapts to whichever color theme you have selected.

Social Login Providers

Clerk makes it easy to add social logins. Kit's auth forms automatically display social login buttons when you enable providers in the Clerk Dashboard.

Google

1

Create OAuth credentials

Go to the Google Cloud Console, create a new OAuth 2.0 Client ID, and set the authorized redirect URI to the value Clerk provides in the dashboard.
2

Enable in Clerk

In the Clerk Dashboard, go to User & Authentication > Social connections, enable Google, and paste your Client ID and Client Secret.
3

Test it

Restart your dev server. The sign-in and sign-up forms now show a "Continue with Google" button.

GitHub

1

Create an OAuth App

Go to GitHub Developer Settings, create a new OAuth App, and set the callback URL to the value Clerk provides.
2

Enable in Clerk

In the Clerk Dashboard, enable GitHub under Social connections and paste your Client ID and Client Secret.
3

Test it

Restart your dev server. The auth forms now include a "Continue with GitHub" button alongside any other providers you have enabled.

Multi-Factor Authentication

To enable MFA for your users:
  1. In the Clerk Dashboard, go to User & Authentication > Multi-factor.
  2. Enable Authenticator app (TOTP) and/or SMS verification.
  3. Users can then enable MFA from their profile settings.
Kit does not require any code changes — Clerk handles the entire MFA flow including setup, verification, and backup codes.

Webhook Setup

Webhooks are how Clerk notifies your application about user events. This is critical for keeping your database in sync — without webhooks, new users would have a Clerk account but no database record.

Create the Webhook Endpoint

1

Open Clerk Webhooks

In the Clerk Dashboard, go to Webhooks and click Add Endpoint.
2

Set the endpoint URL

For local development, use a tunneling service like ngrok or localtunnel:
bash
# Start your tunnel
ngrok http 3000

# Use the tunnel URL as your webhook endpoint:
# https://your-id.ngrok-free.app/api/webhooks/clerk
For production, use your actual domain:
https://yourdomain.com/api/webhooks/clerk
3

Select events

Subscribe to these events:
  • user.created
  • user.updated
  • user.deleted
4

Copy the signing secret

After creating the endpoint, Clerk shows a Signing Secret (starts with whsec_). Copy this and add it to your apps/boilerplate/.env.local:
bash
CLERK_WEBHOOK_SECRET=whsec_your_signing_secret_here

The Webhook Handler

Kit includes a pre-built webhook handler at /api/webhooks/clerk. It receives Clerk events, verifies their authenticity, and updates your database. Here is the signature verification logic:
src/app/api/webhooks/clerk/route.ts
export const POST = withRateLimit('webhooks', async (request: NextRequest) => {
  try {
    // Get the headers
    const headerPayload = await headers()
    const svix_id = headerPayload.get('svix-id')
    const svix_timestamp = headerPayload.get('svix-timestamp')
    const svix_signature = headerPayload.get('svix-signature')

    // If there are no headers, error out
    if (!svix_id || !svix_timestamp || !svix_signature) {
      return new NextResponse('Error occured -- no svix headers', {
        status: 400,
      })
    }

    // Get the body
    const payload = await request.json()
    const body = JSON.stringify(payload)

    // Create a new SVIX instance with your webhook secret
    const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET || '')

    let evt: WebhookEvent

    // Verify the payload with the headers
    try {
      evt = wh.verify(body, {
        'svix-id': svix_id,
        'svix-timestamp': svix_timestamp,
        'svix-signature': svix_signature,
      }) as WebhookEvent
    } catch (err) {
      console.error('Error verifying webhook:', err)
      return new NextResponse('Error occured', {
        status: 400,
      })
    }
The handler is wrapped with withRateLimit('webhooks', ...) to prevent abuse, and uses the Svix library to verify that each request genuinely comes from Clerk.

SVIX Signature Verification

Every Clerk webhook request includes three headers:
HeaderPurpose
svix-idUnique message identifier (for deduplication)
svix-timestampWhen the message was sent (prevents replay attacks)
svix-signatureHMAC signature computed with your webhook secret
The handler verifies these against the request body using your CLERK_WEBHOOK_SECRET. If verification fails, the request is rejected with a 400 status.

Handled Events

When a new user signs up, the user.created event initializes their database record with a Free Tier:
src/app/api/webhooks/clerk/route.ts
if (eventType === 'user.created') {
      const { id, email_addresses, first_name, last_name } = evt.data

      const email = email_addresses[0]?.email_address
      const name = [first_name, last_name].filter(Boolean).join(' ') || null

      // Initialize Free Tier credits
      const freeCredits = parseInt(
        process.env.NEXT_PUBLIC_CREDIT_FREE_TIER_CREDITS || '20'
      )
      const resetDate = new Date()
      resetDate.setDate(resetDate.getDate() + 30) // 30 days from now

      // Create user in database with Free Tier
      await prisma.user.upsert({
        where: { clerkId: id },
        update: {
          email,
          name,
        },
        create: {
          clerkId: id,
          email,
          name,
          // Free Tier initialization
          tier: 'free',
          creditBalance: freeCredits,
          creditsPerMonth: freeCredits,
          creditsResetAt: resetDate,
          bonusCredits: 0,
        },
      })

      console.log(`User ${id} created with Free Tier (${freeCredits} credits)`)
    }
Key details:
  • Upsert operation — Uses prisma.user.upsert instead of create for idempotency. If Clerk retries the webhook, no duplicate records are created.
  • Free Tier credits — New users receive credits defined by NEXT_PUBLIC_CREDIT_FREE_TIER_CREDITS (default: 500).
  • 30-day reset — The credit balance resets 30 days after account creation.
  • Return 200 — Always returns a 200 status to acknowledge receipt. Returning errors causes Clerk to retry, which can create processing storms.
The user.updated event syncs email and name changes, and user.deleted removes the record from the database.

Development Without Clerk

If you have not set up Clerk yet, or you want to develop features without authentication overhead, Kit provides a no-auth development mode:
bash
pnpm dev:no-clerk
This sets NEXT_PUBLIC_CLERK_ENABLED=false, which activates the mock authentication system:
  • A test user is automatically signed in (free@test.example.com)
  • All dashboard routes are accessible without login
  • Clerk SDK is not loaded (faster startup, no API calls)
  • Auth hooks return mock data with identical interfaces
The test user matches the database seed data, so all features (credits, billing, AI chat) work as expected.