or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdclient-runtime.mddatabase.mde2e-testing.mdindex.mdrealtime.mdrouting.mdsynced-state.mdturnstile.mdvite-plugin.mdworker-runtime.md
tile.json

turnstile.mddocs/

Cloudflare Turnstile

Integration with Cloudflare Turnstile CAPTCHA for bot protection with React hooks and server-side verification.

Capabilities

Turnstile Script Component

React component that loads the Cloudflare Turnstile script.

/**
 * Component to load Cloudflare Turnstile script
 */
const TurnstileScript: React.ComponentType;

Usage Example:

import { TurnstileScript } from 'rwsdk/turnstile';

function Document({ children }) {
  return (
    <html>
      <head>
        <title>My App</title>
        <TurnstileScript />
      </head>
      <body>{children}</body>
    </html>
  );
}

Turnstile Hook

React hook for managing Turnstile widget and challenges.

/**
 * React hook for Turnstile widget
 * @param siteKey - Turnstile site key from Cloudflare dashboard
 * @returns Object with ref for widget container and challenge function
 */
function useTurnstile(siteKey: string): {
  /** Ref to attach to the Turnstile widget container div */
  ref: RefObject<HTMLDivElement>;
  /** Function to trigger a challenge and get the token */
  challenge: () => Promise<string>;
};

type RefObject<T> = import('react').RefObject<T>;

Usage Example:

import { useTurnstile } from 'rwsdk/turnstile';
import { useState } from 'react';

function LoginForm() {
  const { ref, challenge } = useTurnstile(process.env.TURNSTILE_SITE_KEY);
  const [error, setError] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      // Get Turnstile token
      const token = await challenge();

      // Submit form with token
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email: e.target.email.value,
          password: e.target.password.value,
          turnstileToken: token,
        }),
      });

      if (!response.ok) {
        setError('Login failed');
      }
    } catch (err) {
      setError('Challenge failed');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" required />
      <input type="password" name="password" required />

      {/* Turnstile widget container */}
      <div ref={ref}></div>

      {error && <p style={{ color: 'red' }}>{error}</p>}

      <button type="submit">Login</button>
    </form>
  );
}

Token Verification (Server)

Verifies a Turnstile token on the server-side.

/**
 * Verifies a Turnstile token server-side
 * @param options - Verification options
 * @returns True if token is valid, false otherwise
 */
function verifyTurnstileToken(options: {
  /** Turnstile token from client */
  token: string;
  /** Turnstile secret key from Cloudflare dashboard */
  secretKey: string;
  /** Optional custom fetch function (for testing) */
  fetchFn?: typeof fetch;
}): Promise<boolean>;

Usage Example:

import { verifyTurnstileToken } from 'rwsdk/turnstile';

// In a route handler
async function loginHandler(request: Request, env: any) {
  const { email, password, turnstileToken } = await request.json();

  // Verify Turnstile token
  const isValidToken = await verifyTurnstileToken({
    token: turnstileToken,
    secretKey: env.TURNSTILE_SECRET_KEY,
  });

  if (!isValidToken) {
    return new Response('Invalid CAPTCHA', { status: 400 });
  }

  // Proceed with login
  const user = await authenticateUser(email, password);

  if (!user) {
    return new Response('Invalid credentials', { status: 401 });
  }

  return Response.json({ success: true, user });
}

Complete Turnstile Example

Document Setup

// Document component with Turnstile script
import { TurnstileScript } from 'rwsdk/turnstile';

function Document({ children }) {
  return (
    <html>
      <head>
        <title>Secure App</title>
        <TurnstileScript />
      </head>
      <body>{children}</body>
    </html>
  );
}

Client Component

// Form with Turnstile protection
import { useTurnstile } from 'rwsdk/turnstile';
import { useState } from 'react';

function ContactForm() {
  const { ref, challenge } = useTurnstile(
    '0x4AAAAAAA...' // Your site key
  );
  const [submitting, setSubmitting] = useState(false);
  const [message, setMessage] = useState('');

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setSubmitting(true);
    setMessage('');

    try {
      const token = await challenge();

      const formData = new FormData(e.currentTarget);
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: formData.get('name'),
          email: formData.get('email'),
          message: formData.get('message'),
          turnstileToken: token,
        }),
      });

      if (response.ok) {
        setMessage('Message sent successfully!');
        e.currentTarget.reset();
      } else {
        setMessage('Failed to send message');
      }
    } catch (error) {
      setMessage('An error occurred');
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" placeholder="Name" required />
      <input type="email" name="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />

      <div ref={ref} />

      <button type="submit" disabled={submitting}>
        {submitting ? 'Sending...' : 'Send'}
      </button>

      {message && <p>{message}</p>}
    </form>
  );
}

Server Handler

// API endpoint with verification
import { verifyTurnstileToken } from 'rwsdk/turnstile';
import { route } from 'rwsdk/router';

const contactRoute = route('/api/contact', {
  POST: async (request: Request, env: any) => {
    const { name, email, message, turnstileToken } = await request.json();

    // Verify Turnstile token
    const isValid = await verifyTurnstileToken({
      token: turnstileToken,
      secretKey: env.TURNSTILE_SECRET_KEY,
    });

    if (!isValid) {
      return new Response('CAPTCHA verification failed', { status: 400 });
    }

    // Process the contact form
    await saveContactMessage({ name, email, message });

    return Response.json({ success: true });
  },
});

Setup Instructions

  1. Get Cloudflare Turnstile Keys:

    • Go to the Cloudflare dashboard
    • Navigate to Turnstile
    • Create a new site
    • Get your Site Key and Secret Key
  2. Configure Environment:

    # wrangler.toml
    [vars]
    TURNSTILE_SITE_KEY = "0x4AAAAAAA..."
    
    # Use secrets for the secret key
    # Run: wrangler secret put TURNSTILE_SECRET_KEY
  3. Add Turnstile Script:

    • Add <TurnstileScript /> to your Document component
    • This loads the Cloudflare Turnstile script
  4. Use in Forms:

    • Use useTurnstile() hook in form components
    • Attach ref to a div where the widget will appear
    • Call challenge() before form submission
  5. Verify on Server:

    • Call verifyTurnstileToken() in your API handlers
    • Only proceed with the action if verification succeeds

Turnstile Modes

Turnstile supports different modes:

  • Managed: Automatic challenge difficulty adjustment
  • Non-interactive: Invisible challenge (no user interaction)
  • Invisible: Runs in the background without showing a widget

The mode is configured in your Cloudflare dashboard when creating the site key.

Error Handling

const { ref, challenge } = useTurnstile(siteKey);

try {
  const token = await challenge();
  // Use token
} catch (error) {
  // Handle challenge failure
  // - User closed the challenge
  // - Network error
  // - Turnstile script not loaded
  console.error('Turnstile challenge failed:', error);
}

Best Practices

  1. Rate Limiting: Combine Turnstile with rate limiting for defense in depth
  2. Error Messages: Don't reveal whether failure was due to CAPTCHA or other validation
  3. Accessibility: Ensure forms remain accessible when Turnstile is enabled
  4. Testing: Use test keys during development (provided by Cloudflare)
  5. Expiration: Tokens expire after a few minutes, verify immediately after receiving