Add Google Recaptcha in Laravel Jetstream and Breeze Registration

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 user IP.
  • Check if the response is successful, and if the returned score is bigger then minimal is set.
  • If everything passes return true, otherwise return false.

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.

avatar

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.

avatar

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.

avatar

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.

avatar
  • For anyone stuck, there's a couple of lines missing from this tutorial (they are in the video).

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/

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 58 courses (1054 lessons, total 46 h 42 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials