FeaturesAuthentication
PreviousNext

Captcha

Protect your app from spam with Cloudflare Turnstile CAPTCHA

CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is a security feature that helps protect your application from automated abuse, spam, and malicious bots.

We use Cloudflare Turnstile CAPTCHA to protect forms from automated abuse and spam.

Setup

Get Cloudflare Turnstile keys at cloudflare.com/products/turnstile

Add environment variables:

# Client-side (safe to expose)
NEXT_PUBLIC_CAPTCHA_SITE_KEY=your_site_key
 
# Server-side (keep secret)
CAPTCHA_SECRET_KEY=your_secret_key

Client Setup

Provider

Wrap your app with CaptchaProvider:

import { CaptchaProvider } from '@kit/auth/captcha/client';
 
export function RootLayout({ children }) {
    return (
        <CaptchaProvider siteKey={process.env.NEXT_PUBLIC_CAPTCHA_SITE_KEY}>
            {children}
        </CaptchaProvider>
    );
}

Options

Customize CAPTCHA appearance:

<CaptchaProvider
    siteKey={process.env.NEXT_PUBLIC_CAPTCHA_SITE_KEY}
    options={{
        options: {
            size: 'normal', // 'normal', 'invisible', 'compact'
            theme: 'light', // 'light', 'dark'
            language: 'en',
        }
    }}
>
    {children}
</CaptchaProvider>
Test

slut

subTest

Hook usage

Use the hook in forms:

'use client'; import { useCaptchaToken } from '@kit/auth/captcha/client'; export function ContactForm() { const { captchaToken, resetCaptchaToken } = useCaptchaToken(); const handleSubmit = async (formData) => { try { await submitAction({ ...formData, captchaToken }); resetCaptchaToken(); } catch (error) { resetCaptchaToken(); // Allow retry } }; return ( <form onSubmit={handleSubmit}> {/* Your form fields */} <button type="submit">Submit</button> </form> ); }

Server Setup

Use captchaActionClient for protected actions:

'use server'; import { captchaActionClient } from '@kit/auth/captcha/server'; import { z } from 'zod'; const schema = z.object({ email: z.string().email(), message: z.string().min(10), // captchaToken is optional and verified automatically }); export const submitContact = captchaActionClient .metadata({ actionName: 'submitContact' }) .schema(schema) .action(async ({ parsedInput: { email, message } }) => { // CAPTCHA already verified if token provided // Your logic here console.log(`Contact: ${email} - ${message}`); return { success: true, message: 'Sent!' }; });

For authenticated actions with CAPTCHA:

import { authCaptchaActionClient } from '@kit/auth/captcha/server';
 
export const protectedAction = authCaptchaActionClient
    .metadata({ actionName: 'protectedAction' })
    .schema(schema)
    .action(async ({ ctx: { db }, parsedInput }) => {
        // Has CAPTCHA verification + authenticated database access
        const user = await db.user.get();
        return { user };
    });

Manual Verification

For custom server logic:

import { verifyCaptcha } from '@kit/auth/captcha/server';
 
export async function customAction(formData: FormData) {
    const token = formData.get('captchaToken');
 
    try {
        await verifyCaptcha(token);
        // Proceed with action
        return { success: true };
    } catch (error) {
        throw new Error('CAPTCHA verification failed');
    }
}

How is this guide?

Last updated on 10/17/2025