To prevent bots from registering into your web app you can add a Captcha. In this tutorial, I will show you how to add Google reCAPTCHA to the register page in Laravel Breeze and Jetstream.
Step 1. Custom Validation Rule
We will validate the recaptcha after submitting the form. So first, we will create a custom validation rule.
php artisan make:rule Recaptcha
This will create a file app/Rules/Recaptcha.php
. In this rule we need to do:
- Send an HTTP post request to Google to verify the captcha. We need to send the
secret key
,recaptcha token
, and userIP
. - Check if the response is successful, and if the returned score is bigger then minimal is set.
- If everything passes return
true
, otherwise returnfalse
.
app/Rules/Recaptcha.php
use Illuminate\Support\Facades\Http; class Recaptcha implements Rule{ public function passes($attribute, $value) { $response = Http::asForm()->post("https://www.google.com/recaptcha/api/siteverify", [ 'secret' => config('services.recaptcha.secret_key'), 'response' => $value, 'ip' => request()->ip(), ]); if ($response->successful() && $response->json('success') && $response->json('score') > config('services.recaptcha.min_score')) { return true; } return false; } public function message() { return 'Failed to validate ReCaptcha.'; }}
We store values from Google reCAPTCHA into config/services.php
:
return [ // ... 'recaptcha' => [ 'site_key' => env('RECAPTCHA_SITE_KEY'), 'secret_key' => env('RECAPTCHA_SECRET_KEY'), 'min_score' => env('RECAPTCHA_MIN_SCORE', .5), ],];
Step 2. Prepare Layout for reCAPTCHA
To use reCAPTCHA we need to add some scripts.
resources/views/layouts/guest.blade.php:
<!DOCTYPE html><html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Fonts --> <link rel="preconnect" href="https://fonts.bunny.net"> <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" /> <!-- Scripts --> @vite(['resources/css/app.css', 'resources/js/app.js']) <script src="https://www.google.com/recaptcha/api.js?render={{ config('services.recaptcha.site_key') }}"></script> </head> <body> <div class="font-sans text-gray-900 antialiased"> {{ $slot }} </div> @stack('scripts') </body></html>
resources/views/auth/register.blade.php:
<x-guest-layout> <form method="POST" action="{{ route('register') }}"> <form method="POST" action="{{ route('register') }}" id="registerForm"> @csrf // ... @push('scripts') <script> grecaptcha.ready(function () { document.getElementById('registerForm').addEventListener("submit", function (event) { event.preventDefault(); grecaptcha.execute('{{ config('services.recaptcha.site_key') }}', { action: 'register' }) .then(function (token) { document.getElementById("recaptcha_token").value = token; document.getElementById('registerForm').submit(); }); }); }); </script> @endpush </x-guest-layout>
Step 3. Use Validation Rule
All that is left is to add our created custom validation rule to usage. In Breeze and Jetstream, registration is done in different files.
Laravel Breeze
app/Http/Controllers/Auth/RegisteredUserController.php:
use App\Rules\Recaptcha; class RegisteredUserController extends Controller{ public function create(): View { return view('auth.register'); } public function store(Request $request): RedirectResponse { $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class], 'password' => ['required', 'confirmed', Rules\Password::defaults()], 'recaptcha_token' => ['required', new Recaptcha()] ]); $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); event(new Registered($user)); Auth::login($user); return redirect(RouteServiceProvider::HOME); }}
Laravel Jetstream
app/Actions/Fortify/CreateNewUser.php:
use App\Rules\Recaptcha; class CreateNewUser implements CreatesNewUsers{ use PasswordValidationRules; public function create(array $input): User { Validator::make($input, [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => $this->passwordRules(), 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '', 'recaptcha_token' => ['required', new Recaptcha()], ])->validate(); return User::create([ 'name' => $input['name'], 'email' => $input['email'], 'password' => Hash::make($input['password']), ]); }}
That's it for this tutorial. I hope now your web app will have no bots.
Great article!
I am implementing this in all of my projects.
However, recently I converted a form to a Livewire component and I can't seem to get Google Recaptcha to work.
It would be great if you could also write something about this.
It sounds like needs debugging for your personal project, we can't really guess how you structured the code in your Livewire component, so can't write about exactly your situation.
Unless you put your code somewhere on GitHub so we would be able to reproduce and then provide advice with a tutorial.
Thank you, Povilas.
With the help of ChatGPT, I was able to solve the problem.
Instead of using Livewire, I directly used Alpine JS.
I performed axios post operations within the grecaptcha.execute expression.
Previously, I couldn't send the token value received from Google to the backend in any way.
need to add a recaptcha_token hidden field <input type="hidden" id="recaptcha_token" name="recaptcha_token">
on the CreateNewUser validation, you need to pass the $input['recaptcha_token'] into the Recaptcha class. Ex.
'recaptcha_token' => ['required', new Recaptcha($input['recaptcha_token'])],
Lastly, I found myself stuck trying to work with "Recatcha Enterprise" accidentally. This tutorial uses the base Recaptcha features, not Enterprise. https://console.cloud.google.com/security/recaptcha/
I would love to know how to combine this technique with this package: https://github.com/spatie/laravel-honeypot
Hi, not sure, as I we haven't used the package
Ok understood. I'll give it a spin. For now I've reached out to their community forum: https://github.com/spatie/laravel-honeypot/discussions/133
I did try the honeypot with Statamic, but the results was not good. A lot of spam was received.