---
title: "Bot-Schutz in Astro mit Cloudflare Turnstile"
description: "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."
source: "https://www.launchfa.st/de/blog/astro-turnstile-bot-protection"
author: "Rishi Raj Jain"
created_at: 2025-12-09T12:00:00.000Z
keywords: "astro, cloudflare turnstile, bot protection, captcha alternative, form security, spam prevention"
---

> **Site index for agents**
> Fetch https://www.launchfa.st/llms.txt to discover every page on this site.
> Every page is also available as Markdown by appending `.md` to its URL.

<img width="1600" height="900" alt="Bot-Schutz in Astro mit Cloudflare Turnstile" decoding="async" loading="eager" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/astro-turnstile-bot-protection.png" />

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.

[Cloudflare Turnstile](https://developers.cloudflare.com/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.

- [GitHub-Beispielprojekt ansehen](https://github.com/rishi-raj-jain/my-astro-turnstile-app)

## Voraussetzungen

Sie benötigen:

- [Node.js 20](https://nodejs.org/en/blog/announcements/v20-release-announce) oder höher
- Ein [Cloudflare](https://dash.cloudflare.com/sign-up)-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 + metadata
```

## Turnstile-API-Schlüssel erhalten

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

1. Öffnen Sie das [Cloudflare-Dashboard](https://dash.cloudflare.com/)
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:

```bash
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:

```bash
npm run dev
```

Die App sollte auf [localhost:4321](http://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:

```bash
npx astro add cloudflare --yes
```

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

```javascript
// File: 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:

```jsonc
// File: 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:

```bash
# File: .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:

```bash
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:

```typescript
// File: 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`:

```typescript
// File: 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:

```typescript
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`:

```typescript
// File: 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:

```astro
---
// File: 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

```html
<!-- 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

```html
<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](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#error-codes-reference):

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

```typescript
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:

```bash
# 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](/blog/rate-limiting-astro-cloudflare-workers) 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](https://twitter.com/direct_messages/create/rishi_raj_jain_).
