PHP Classes

File: docs/laravel.md

Recommend this page to a friend!
  Classes of Eric Sizemore   Cloudflare Turnstile   docs/laravel.md   Download  
File: docs/laravel.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: Cloudflare Turnstile
Verify human users with Cloudflare Turnstile
Author: By
Last change:
Date: 2 months ago
Size: 5,553 bytes
 

 

Contents

Class file image Download

Laravel Integration Guide

This guide demonstrates how to integrate the Cloudflare Turnstile library with Laravel 11.x.

> [!IMPORTANT] > Guide is a work in progress. I have not been able to fully test this integration.

Installation

# Install the Turnstile library with Guzzle (recommended for Laravel)
composer require esi/cloudflare-turnstile guzzlehttp/guzzle:^7.0

Configuration

Add your Turnstile credentials to your .env file:

TURNSTILE_SITE_KEY=your_site_key_here
TURNSTILE_SECRET_KEY=your_secret_key_here

Create a new configuration file config/services.php or add to existing:

return [
    // ... other services
    'turnstile' => [
        'site_key' => env('TURNSTILE_SITE_KEY'),
        'secret_key' => env('TURNSTILE_SECRET_KEY'),
    ],
];

Service Provider

Create a new service provider:

namespace App\Providers;

use Esi\CloudflareTurnstile\Turnstile;
use Esi\CloudflareTurnstile\ValueObjects\SecretKey;
use Illuminate\Support\ServiceProvider;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;

class TurnstileServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(Turnstile::class, function ($app) {
            $factory = new HttpFactory();
            
            return new Turnstile(
                new Client(),
                $factory,
                $factory,
                new SecretKey(config('services.turnstile.secret_key'))
            );
        });
    }
}

Register the service provider in config/app.php:

'providers' => [
    // ...
    App\Providers\TurnstileServiceProvider::class,
],

Form Request Validation

Create a form request validator:

namespace App\Http\Requests;

use Esi\CloudflareTurnstile\Turnstile;
use Esi\CloudflareTurnstile\ValueObjects\IpAddress;
use Esi\CloudflareTurnstile\ValueObjects\Token;
use Esi\CloudflareTurnstile\VerifyConfiguration;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;

class TurnstileFormRequest extends FormRequest
{
    /
     * @return array<string, ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'cf-turnstile-response' => ['required', 'string'],
            // ... other validation rules
        ];
    }

    public function validateTurnstile(Turnstile $turnstile): void
    {
        $config = new VerifyConfiguration(
            new Token($this->input('cf-turnstile-response')),
            new IpAddress($this->ip())
        );

        try {
            $response = $turnstile->verify($config);

            if (!$response->isSuccess()) {
                throw ValidationException::withMessages([
                    'turnstile' => ['The security challenge was not completed successfully.']
                ]);
            }
        } catch (\Exception $e) {
            throw ValidationException::withMessages([
                'turnstile' => ['An error occurred while validating the security challenge.']
            ]);
        }
    }
}

Controller Implementation

namespace App\Http\Controllers;

use App\Http\Requests\TurnstileFormRequest;
use Esi\CloudflareTurnstile\Turnstile;
use Illuminate\Http\RedirectResponse;

class LoginController extends Controller
{
    public function login(TurnstileFormRequest $request, Turnstile $turnstile): RedirectResponse
    {
        $request->validateTurnstile($turnstile);

        // Your login logic here
        
        return redirect()->intended('/dashboard');
    }
}

Blade Template Integration

<form method="POST" action="{{ route('login') }}">
    @csrf
    
    <!-- Your form fields -->
    
    <div class="cf-turnstile" data-sitekey="{{ config('services.turnstile.site_key') }}"></div>
    
    @error('turnstile')
        <div class="alert alert-danger">{{ $message }}</div>
    @enderror
    
    <button type="submit">Login</button>
</form>

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

Testing

For testing, you may want to mock the Turnstile service:

namespace Tests\Feature;

use Esi\CloudflareTurnstile\Turnstile;
use Esi\CloudflareTurnstile\Response;
use Tests\TestCase;

class LoginTest extends TestCase
{
    public function test_login_with_turnstile(): void
    {
        $this->mock(Turnstile::class, function ($mock) {
            $mock->shouldReceive('verify')
                ->once()
                ->andReturn(new Response([
                    'success' => true,
                    'challenge_ts' => '2024-12-24T00:12:00Z',
                    'hostname' => 'localhost',
                    'error-codes' => [],
                ]));
        });

        $response = $this->post('/login', [
            'email' => 'test@example.com',
            'password' => 'password',
            'cf-turnstile-response' => 'test-token',
        ]);

        $response->assertRedirect('/dashboard');
    }
}

Error Handling

The Turnstile integration will handle various error cases:

* Missing token response * Invalid token response * Network errors * Server-side validation errors

These are handled through Laravel's validation system and will be available in your views through the @error directive.

Security Considerations

  • Always validate the Turnstile response server-side
  • Use HTTPS in production
  • Keep your secret key secure
  • Consider rate limiting your endpoints