Using Cloudflare Turnstile with Livewire

September 11th, 2024 • 4 minutes read time

Setting up, configuring and validating a Cloudflare Turnstile challenge with Livewire is simple, thanks to the Laravel-cloudflare-turnstile package. Let's look at how to set it up!

Prefer to watch? This article is available as a free course!

Imagine we're building a form for users to subscribe to a newsletter.

Here's the component and template we'll be working with, although it doesn't matter how you've set this up or what data you're submitting through a form. I've used Laravel Breeze as the starter kit to access some pre-defined components like text-input you see here.

<form wire:submit="store">
    <div>
        <x-text-input class="h-9 w-full" placeholder="e.g. alex@codecourse.com" wire:model="email" />
        @error('email')
            <div class="text-sm text-red-500">
                {{ $message }}
            </div>
        @enderror
    </div>
</form>

Here's what the Livewire component looks like.

class NewsletterIndex extends Component
{
    public string $email = '';

    public function rules()
    {
        return [
            'email' => 'required',
        ];
    }

    public function store()
    {
        $this->validate();

        dd('Subscribe');
    }

    #[Layout('layouts.app')]
    public function render()
    {
        return view('livewire.newsletter-index');
    }
}

So right now, we're submitting an email through, validating it and then using dd so we know we're through. I've purposely not used a Livewire attribute to validate for reasons we'll see later.

Create a Turnstile widget in your Cloudflare account if you still need to. Here's how I set mine up:

01.webp

Choose any settings you like, and once you're done, grab the Site key and Secret key and store them somewhere safe temporarily.

To make things easier, we'll be using the laravel-cloudflare-turnstile package to render and validate a Turnstile challenge. This integrates nicely with Livewire, too.

Start by installing the package:

composer require ryangjchandler/laravel-cloudflare-turnstile

Next, head to config/services.php in your Laravel application and add a turnstile service:

'turnstile' => [
    'key' => env('TURNSTILE_SITE_KEY'),
    'secret' => env('TURNSTILE_SECRET_KEY'),
],

Now take your Site key and Secret key from earlier and add them to your .env file:

TURNSTILE_SITE_KEY=YOUR_SITE_KEY_HERE
TURNSTILE_SECRET_KEY=YOUR_SECRET_KEY_HERE

Great, we're configured!

Cloudflare Turnstile requires a script to be added to the head of the pages you need to use the widget. To make things easier (and since we're using Breeze with an app layout), let's add it to resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <!-- Other stuff -->

        <!-- Scripts -->
        @vite(['resources/css/app.css', 'resources/js/app.js'])
        @turnstileScripts()
    </head>

Bear in mind that Breeze and other starter kits may have a separate guest layout, so make sure the script directive is added to every page/layout you need.

And that's Turnstile set up. Let's show a challenge!

Head straight over to the form you want to integrate the challenge into and use the turnstile component to render it out. We'll look at how to configure it later.

<form wire:submit="store">
    <div>
        <x-text-input class="h-9 w-full" placeholder="e.g. alex@codecourse.com" wire:model="email" />
        @error('email')
            <div class="text-sm text-red-500">
                {{ $message }}
            </div>
        @enderror
    </div>

    <div class="mt-2">
        <div wire:ignore>
            <x-turnstile />
        </div>
    </div>

    <x-primary-button class="mt-2">Subscribe</x-primary-button>
</form>

Notice I've wrapped this in a separate div and added wire:ignore? That's because we don't want the widget to re-render when we submit the form with Livewire. If any other validation fails or any other requests are sent, re-rendering the widget would cause issues.

You should now see the widget being rendered out on your form like this:

02.webp

The widget from the laravel-cloudflare-turnstile package allows the value of the validated challenge to be set as a model, so let's add that first so we can access the value in our Livewire component:

<div wire:ignore>
    <x-turnstile
        wire:model="turnstileResponse"
    />
</div>

Now hook this up to a property in your component:

class NewsletterIndex extends Component
{
    public string $email = '';

    public string $turnstileResponse = '';

    // ...
}

Once that's done, use the turnstile rule method to validate the response:

use Illuminate\Validation\Rule;

public function rules()
{
    return [
        'email' => 'required',
        'turnstileResponse' => ['required', Rule::turnstile()]
    ];
}

You should now be able to submit the form and have the response from Turnstile validated! If this doesn't work for any reason, dump out the value of turnstileResponse in your template to see if it contains a value.

Oh, and if you'd like to add some custom validation rules to your Turnstile challenge, just implement a messages method:

use RyanChandler\LaravelCloudflareTurnstile\Rules\Turnstile;

public function messages()
{
  return [
	'turnstileResponse.required' => 'Turnstile required',
	Turnstile::class => 'Turnstile failed'
  ];
}

If the challenge fails for any reason, the user will see Turnstile failed as the validation error. Make sure you add it to your template, too:

<div wire:ignore>
    <x-turnstile
        wire:model="turnstileResponse"
    />
</div>

@error('turnstileResponse')
    <div class="text-sm text-red-500">
        {{ $message }}
    </div>
@enderror

You can pass plenty of configuration options to the widget, which you can find in the docs for this package. For now, let's add the action and theme options:

<x-turnstile
    wire:model="turnstileResponse"
    data-action="newsletter"
    data-theme="light"
/>

03.webp

Much better!

So, adding Cloudflare Turnstile to your Livewire applications isn't too much trouble. This component can also be used without Livewire.

If you found this article helpful, you'll love our practical screencasts.
Author
Alex Garrett-Smith
Share :

Comments

No comments, yet. Be the first!