Bot-Schutz in Astro mit Cloudflare Turnstile
LaunchFast Logo LaunchFast
Blog
2.180 Wörter 11 Min. Lesezeit

Bot-Schutz in Astro mit Cloudflare Turnstile

Ersetzen Sie CAPTCHA durch unsichtbare Bot-Erkennung in Astro mit Cloudflare Turnstile. Schützen Sie Registrierungsformulare, Login-Seiten und API-Endpunkte vor Bots, ohne echte Nutzer zu frustrieren.

Rishi Raj Jain
Rishi Raj Jain Autor
Bot-Schutz in Astro mit Cloudflare Turnstile

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.

Sponsored

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 ↗

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:

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 + metadata

Turnstile-API-Schlüssel erhalten

Bevor Sie loslegen, benötigen Sie Turnstile-API-Schlüssel:

  1. Öffnen Sie das Cloudflare-Dashboard
  2. Navigieren Sie in der Seitenleiste zu Application security > Turnstile
  3. Klicken Sie auf Add site
  4. 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)
  5. 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:

Terminal window
npm create astro@latest my-astro-turnstile-app
cd my-astro-turnstile-app
npm install wrangler

Wenn 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:

Terminal window
npm run dev

Die 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:

Terminal window
npx astro add cloudflare --yes

Aktualisieren Sie astro.config.mjs, sodass output auf server gesetzt ist:

astro.config.mjs
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:

wrangler.jsonc
{
"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:

.dev.vars
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:

Terminal window
npx wrangler secret put TURNSTILE_SITE_KEY
npx wrangler secret put TURNSTILE_SECRET_KEY

TypeScript-Typen hinzufügen

Erstellen Sie src/env.d.ts, um passende Typen für Umgebungsvariablen hinzuzufügen:

src/env.d.ts
/// <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:

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:

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:

src/pages/contact.astro
---
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 CodeBedeutungLösung
missing-input-secretSecret Key nicht angegebenUmgebungsvariablen prüfen
invalid-input-secretUngültiger Secret KeySecret Key verifizieren
missing-input-responseToken nicht angegebenSicherstellen, dass das Formular ein Token sendet
invalid-input-responseUngültiges oder abgelaufenes TokenNutzer bitten, die Verifizierung erneut durchzuführen
timeout-or-duplicateToken bereits verwendet oder abgelaufenNeues 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:

Terminal window
# Build the project
npm run build
# Deploy to Cloudflare Workers
npx wrangler deploy

Ihre 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.

Weiterlesen