Skip to main content
Tutorial Free

Add Google Recaptcha in Laravel Jetstream and Breeze Registration

March 13, 2023
4 min read

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.

Enjoyed This Tutorial?

Get access to all premium tutorials, video and text courses, and exclusive Laravel resources. Join our community of 10,000+ developers.

Recent Courses on Laravel Daily

Next.js Basics for Laravel Developers

11 lessons
58 min

Laravel 13 Eloquent: Expert Level

41 lessons
1 h 34 min

Queues in Laravel 13

18 lessons
1 h 12 min read
Boran 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.

Povilas Korop 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.

Boran 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.

Matthew Peavoy 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/

Eugene van der Merwe  avatar
Eugene van der Merwe

I would love to know how to combine this technique with this package: https://github.com/spatie/laravel-honeypot

Modestas avatar

Hi, not sure, as I we haven't used the package

Eugene van der Merwe  avatar
Eugene van der Merwe

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

Ali Awwad avatar

I did try the honeypot with Statamic, but the results was not good. A lot of spam was received.