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');
}
}
User Settings
Display the authentication user settings UI.
Emails
All you need to know to setup your SMTP server.
How is this guide?
Last updated on 10/17/2025