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:
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:
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"
/>
Much better!
So, adding Cloudflare Turnstile to your Livewire applications isn't too much trouble. This component can also be used without Livewire.