Klassische CAPTCHAs schützen Formulare vor Bots, frustrieren aber auch legitime Nutzer mit Rätseln, Bildauswahl und schlechter Barrierefreiheit. Google reCAPTCHA v2 ist berüchtigt dafür, Nutzer so lange Ampeln anklicken zu lassen, bis sie aufgeben. Selbst „unsichtbare“ CAPTCHAs schlagen oft still fehl oder verschlechtern die Nutzererfahrung.
Hochwertige Starter-Kits mit integriertem Authentifizierungsfluss (Auth.js), Objekt-Uploads (AWS, Clouflare R2, Firebase Storage, Supabase Storage), integrierten Zahlungen (Stripe, LemonSqueezy), E-Mail-Verifizierungsablauf (Resend, Postmark, Sendgrid) und viel mehr . Kompatibel mit jeder Datenbank (Redis, Postgres, MongoDB, SQLite, Firestore).
Get all 3 kits Bundle ↗- Next.js Starter Kit
- SvelteKit Starter Kit
- Astro Starter Kit
One-time license · Lifetime updates
Cloudflare Turnstile ersetzt CAPTCHAs durch intelligente, datenschutzfreundliche Bot-Erkennung. Es läuft im Hintergrund und analysiert Browser-Verhalten und Geräte-Fingerprints, ohne Nutzer zu Rätseln aufzufordern. Die meisten legitimen Nutzer sehen nie eine Challenge. Wenn doch eine erscheint, ist es eine einfache Checkbox (keine Bildergalerie mit Hydranten).
In dieser Anleitung integrieren Sie Turnstile in eine Astro-Anwendung, um Registrierungsformulare, Kontaktformulare und API-Endpunkte zu schützen. Sie fügen das Widget zu Ihren Formularen hinzu, verifizieren Tokens serverseitig und behandeln Randfälle wie Rate Limiting und Fehlerzustände. Am Ende haben Sie produktionsreifen Bot-Schutz, der global auf Cloudflares Netzwerk skaliert.
Voraussetzungen
Sie benötigen:
- Node.js 20 oder höher
- Ein Cloudflare-Konto
Architektur-Überblick
1. Frontend (Astro component with Turnstile widget) → Renders Turnstile widget on form → Captures token when user completes challenge
2. Backend (API route or form handler) → Receives form data + Turnstile token → Verifies token with Cloudflare API → Processes request only if verification succeeds
3. Cloudflare Turnstile API → Validates token → Returns success/failure + metadataTurnstile-API-Schlüssel erhalten
Bevor Sie loslegen, benötigen Sie Turnstile-API-Schlüssel:
- Öffnen Sie das Cloudflare-Dashboard
- Navigieren Sie in der Seitenleiste zu Application security > Turnstile
- Klicken Sie auf Add site
- Konfigurieren Sie Ihre Site:
- Site name:
my-astro-turnstile-app - Domain: Fügen Sie Ihre Domain hinzu (z. B. workers.dev)
- Widget Mode: Wählen Sie Managed (empfohlen für die meisten Anwendungsfälle)
- Site name:
- Klicken Sie auf Create
Sie erhalten:
- Site Key (öffentlich, wird im Frontend verwendet)
- Secret Key (privat, wird im Backend verwendet)
Speichern Sie diese Schlüssel — Sie benötigen sie in den nächsten Schritten.
Eine neue Astro-Anwendung erstellen
Beginnen wir mit der Erstellung eines neuen Astro-Projekts. Führen Sie den folgenden Befehl aus:
npm create astro@latest my-astro-turnstile-appcd my-astro-turnstile-appnpm install wranglerWenn Sie dazu aufgefordert werden, wählen Sie:
Use minimal (empty) template, wenn Sie gefragt werden, wie das neue Projekt gestartet werden soll.Yes, wenn Sie aufgefordert werden, Abhängigkeiten zu installieren.Yes, wenn Sie aufgefordert werden, ein Git-Repository zu initialisieren.
Sobald das erledigt ist, können Sie in das Projektverzeichnis wechseln und die App starten:
npm run devDie App sollte auf localhost:4321 laufen.
Cloudflare-Adapter in Ihr Astro-Projekt integrieren
Um Ihr Astro-Projekt auf Cloudflare Workers zu deployen und Umgebungsvariablen sicher zu nutzen, müssen Sie den Cloudflare-Adapter installieren. Führen Sie den folgenden Befehl aus:
npx astro add cloudflare --yesAktualisieren Sie astro.config.mjs, sodass output auf server gesetzt ist:
import { defineConfig } from 'astro/config';import cloudflare from '@astrojs/cloudflare';
export default defineConfig({ output: 'server', adapter: cloudflare()});Umgebungsvariablen konfigurieren
Fügen Sie Ihre Turnstile-Schlüssel zu wrangler.jsonc hinzu:
{ "main": "dist/_worker.js/index.js", "name": "my-astro-turnstile-app", "compatibility_date": "2025-12-08", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" ], "assets": { "binding": "ASSETS", "directory": "./dist" }, "observability": { "enabled": true }, "vars": { "TURNSTILE_SITE_KEY": "...", "TURNSTILE_SECRET_KEY": "..." }}Wichtig: Committen Sie Ihren Secret Key niemals in die Versionskontrolle. Für die lokale Entwicklung erstellen Sie eine
.dev.vars-Datei:
TURNSTILE_SITE_KEY="your-site-key"TURNSTILE_SECRET_KEY="your-secret-key"Für die Produktion setzen Sie das Secret über das Cloudflare-Dashboard oder die CLI:
npx wrangler secret put TURNSTILE_SITE_KEYnpx wrangler secret put TURNSTILE_SECRET_KEYTypeScript-Typen hinzufügen
Erstellen Sie src/env.d.ts, um passende Typen für Umgebungsvariablen hinzuzufügen:
/// <reference types="astro/client" />
type ENV = { TURNSTILE_SITE_KEY: string TURNSTILE_SECRET_KEY: string}
type Runtime = import('@astrojs/cloudflare').Runtime<ENV>
declare namespace App { interface Locals extends Runtime { }}Damit erhalten Sie vollständige Typsicherheit beim Zugriff auf locals.runtime.env.TURNSTILE_SITE_KEY und locals.runtime.env.TURNSTILE_SECRET_KEY in Ihren Routes.
Eine Verifizierungs-Utility erstellen
Für Wiederverwendbarkeit extrahieren Sie die Turnstile-Verifizierungslogik in eine Utility-Funktion. Erstellen Sie src/lib/turnstile.ts:
interface TurnstileResponse { success: boolean challenge_ts?: string hostname?: string 'error-codes'?: string[] action?: string cdata?: string}
/** * Verify a Turnstile token with Cloudflare's API */export async function verifyTurnstileToken( token: string, secretKey: string, remoteIP?: string): Promise<TurnstileResponse> { const response = await fetch( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ secret: secretKey, response: token, remoteip: remoteIP, // Optional: pass client IP for additional validation }), } )
if (!response.ok) { throw new Error(`Turnstile API error: ${response.status}`) }
return response.json()}
/** * Extract Turnstile token from form data or request body */export function getTurnstileToken(formData: FormData): string | null { return formData.get('cf-turnstile-response') as string | null}
/** * Helper to check if Turnstile verification is required */export function isTurnstileEnabled(env: any): boolean { return !!(env.TURNSTILE_SITE_KEY && env.TURNSTILE_SECRET_KEY)}Jetzt können Sie diese Utility in jedem Formular-Handler oder jeder API-Route verwenden:
import { verifyTurnstileToken, getTurnstileToken } from '../lib/turnstile'
const token = getTurnstileToken(formData)if (!token) { return new Response('Missing verification token', { status: 400 })}
const result = await verifyTurnstileToken( token, Astro.locals.runtime.env.TURNSTILE_SECRET_KEY)
if (!result.success) { return new Response('Verification failed', { status: 403 })}
// Proceed with form processing...API-Endpunkte schützen
Sie können API-Endpunkte ebenfalls mit Turnstile schützen. Das ist nützlich, um automatisierten Missbrauch öffentlicher APIs zu verhindern.
Erstellen Sie src/pages/api/contact.ts:
import type { APIRoute } from 'astro'import { verifyTurnstileToken } from '../../lib/turnstile'
export const POST: APIRoute = async ({ request, locals }) => { try { const body = await request.json() const { email, message, token } = body
// Validate required fields if (!email || !message) { return new Response( JSON.stringify({ error: 'Email and message are required' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ) }
// Verify Turnstile token if (!token) { return new Response( JSON.stringify({ error: 'Verification token missing' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ) }
const secretKey = locals.runtime.env.TURNSTILE_SECRET_KEY const clientIP = request.headers.get('CF-Connecting-IP') || undefined
const verification = await verifyTurnstileToken(token, secretKey, clientIP)
if (!verification.success) { return new Response( JSON.stringify({ error: 'Verification failed', codes: verification['error-codes'], }), { status: 403, headers: { 'Content-Type': 'application/json' } } ) }
// Process the contact form (e.g., send email, save to database) console.log('Valid contact form submission:', email)
return new Response( JSON.stringify({ success: true, message: 'Message received' }), { status: 200, headers: { 'Content-Type': 'application/json' } } ) } catch (error) { console.error('Contact API error:', error) return new Response( JSON.stringify({ error: 'Internal server error' }), { status: 500, headers: { 'Content-Type': 'application/json' } } ) }}Frontend-Formular, das diese API aufruft:
---const siteKey = import.meta.env.PROD ? Astro.locals.runtime.env.TURNSTILE_SITE_KEY : import.meta.env.TURNSTILE_SITE_KEY;---
<html lang="en"> <head> <meta charset="UTF-8" /> <title>Contact Us</title> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> </head> <body> <h1>Contact Us</h1> <form id="contact-form"> <input type="email" name="email" placeholder="Your email" required /> <textarea name="message" placeholder="Your message" required></textarea> <div class="cf-turnstile" data-sitekey={siteKey}></div> <button type="submit">Send Message</button> </form> <script> const form = document.getElementById('contact-form'); form?.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(form); const token = formData.get('cf-turnstile-response'); const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, email: formData.get('email'), message: formData.get('message'), }), }); const result = await response.json(); if (result.success) { alert('Message sent successfully!'); form.reset(); } else alert('Error: ' + result.error); }); </script> </body></html>Widget-Anpassung
Turnstile bietet verschiedene Widget-Modi und Themes:
Widget-Modi
<!-- Managed (recommended): Shows challenge only when needed --><div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<!-- Non-Interactive: Invisible, always runs in background --><div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY" data-appearance="interaction-only"></div>Callbacks
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY" data-callback="onTurnstileSuccess" data-error-callback="onTurnstileError" data-expired-callback="onTurnstileExpired"></div>
<script> function onTurnstileSuccess(token) { console.log('Turnstile verified:', token); }
function onTurnstileError(error) { console.error('Turnstile error:', error); }
function onTurnstileExpired() { console.warn('Turnstile token expired'); }</script>Fehlerbehandlung
Häufige Turnstile-Fehlercodes:
| Error Code | Bedeutung | Lösung |
|---|---|---|
missing-input-secret | Secret Key nicht angegeben | Umgebungsvariablen prüfen |
invalid-input-secret | Ungültiger Secret Key | Secret Key verifizieren |
missing-input-response | Token nicht angegeben | Sicherstellen, dass das Formular ein Token sendet |
invalid-input-response | Ungültiges oder abgelaufenes Token | Nutzer bitten, die Verifizierung erneut durchzuführen |
timeout-or-duplicate | Token bereits verwendet oder abgelaufen | Neues Token generieren |
Fehler elegant behandeln:
const verification = await verifyTurnstileToken(token, secretKey)
if (!verification.success) { const errorCode = verification['error-codes']?.[0] switch (errorCode) { case 'timeout-or-duplicate': return new Response('Token expired. Please refresh the page.', { status: 400 }) case 'invalid-input-response': return new Response('Invalid verification. Please try again.', { status: 400 }) default: return new Response('Verification failed. Please contact support.', { status: 500 }) }}Auf Cloudflare Workers deployen
Deployen Sie Ihre bot-geschützte Anwendung in die Produktion:
# Build the projectnpm run build
# Deploy to Cloudflare Workersnpx wrangler deployIhre Formulare sind jetzt in der Produktion geschützt.
Best Practices
- Immer serverseitig verifizieren: Vertrauen Sie niemals allein auf clientseitige Validierung.
- Managed-Modus verwenden: Lassen Sie Turnstile entscheiden, wann Challenges angezeigt werden.
- Fehler elegant behandeln: Zeigen Sie nutzerfreundliche Meldungen, keine rohen Fehlercodes.
- Mit Rate Limiting kombinieren: Fügen Sie Rate Limiting für zusätzlichen Schutz hinzu.
Fazit
Cloudflare Turnstile ersetzt frustrierende CAPTCHAs durch unsichtbaren, datenschutzfreundlichen Bot-Schutz. Durch die Integration von Turnstile in Ihre Astro-Formulare und API-Endpunkte blockieren Sie automatisierten Missbrauch, ohne die Nutzererfahrung für legitime Besucher zu verschlechtern.
Das Muster, das Sie aufgebaut haben — clientseitiges Widget + serverseitige Verifizierung + elegante Fehlerbehandlung — skaliert problemlos auf jedes Formular oder jede API. Fügen Sie es zu Login-Seiten, Checkout-Flows, Kommentarbereichen oder jedem Endpunkt hinzu, der Schutz vor Bots benötigt.
Bei Fragen oder Anmerkungen erreichen Sie mich gerne auf Twitter.