Adding reCAPTCHA to your Laravel application is important to protect your forms from spam and bot attacks. In this article, I'll show you how to implement Google reCAPTCHA v2 in your Laravel authentication system.
Prerequisites
- A Laravel application
- A Google reCAPTCHA v2 site key and secret key. Here is how to get it quick:
Go to https://developers.google.com/recaptcha and click 'Get Started'
Enter your app information, choose the v2 -> 'Not a robot' option and click 'Submit'.
You will see the site and secret keys. Or you can access it in your console at https://console.cloud.google.com/security/recaptcha
If you want to know the difference between the v3 and the v2 options, ask Google ;)
Step 1: Configure reCAPTCHA Keys
First, add your reCAPTCHA configuration to your .env
file:
RECAPTCHA_SITE_KEY=your_site_key_here
RECAPTCHA_SECRET_KEY=your_secret_key_here
Create a new config file config/captcha.php
:
<?php
return [
'sitekey' => env('RECAPTCHA_SITE_KEY'),
'secret' => env('RECAPTCHA_SECRET_KEY'),
];
Step 2: Create a Validation Trait
Create a new trait to handle reCAPTCHA validation:
<?php
namespace App\Traits;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;
trait RecaptchaValidation
{
/**
* Validate reCAPTCHA token for a specific action
*
* @param string $token
* @param string $action
* @return bool
* @throws ValidationException
*/
protected function validateRecaptcha(string $token, string $action = 'DEFAULT'): bool
{
// Retrieve environment variables for Google Cloud Project ID and reCAPTCHA Site Key
$recaptchaKey = config('captcha.sitekey');
$project = config('services.google.project_id');
// Create the reCAPTCHA client and project name
$options = [
'credentials' => config('services.google.service_account_credentials'),
];
try {
$client = new RecaptchaEnterpriseServiceClient($options);
$projectName = $client->projectName($project);
// Set the event for the reCAPTCHA assessment
$event = (new Event())
->setSiteKey($recaptchaKey)
->setToken($token);
// Create the assessment
$assessment = (new \Google\Cloud\RecaptchaEnterprise\V1\Assessment())
->setEvent($event);
$response = $client->createAssessment($projectName, $assessment);
// Check if the token is valid
if (!$response->getTokenProperties()->getValid()) {
$invalidReason = InvalidReason::name($response->getTokenProperties()->getInvalidReason());
Log::error("reCAPTCHA token invalid: $invalidReason");
return false;
}
// Check if the action matches the expected action
if ($response->getTokenProperties()->getAction() !== $action) {
Log::error("reCAPTCHA action mismatch. Action: $action and expected action: " . $response->getTokenProperties()->getAction());
return false;
}
// Check the risk score
$riskScore = $response->getRiskAnalysis()->getScore();
Log::info("reCAPTCHA risk score for {$action}: {$riskScore}");
return $riskScore >= config('app.recaptcha.threshold');
} catch (\Exception $e) {
Log::error("reCAPTCHA assessment failed for {$action}: " . $e->getMessage());
return false;
}
}
/**
* Validate reCAPTCHA token and throw exception if invalid
*
* @param string $token
* @param string $action
* @throws ValidationException
*/
protected function validateRecaptchaOrFail(string $token, string $action = 'DEFAULT'): void
{
if (!$this->validateRecaptcha($token, $action)) {
throw ValidationException::withMessages([
'g-recaptcha-response' => ['reCAPTCHA verification failed.'],
]);
}
}
}
Step 3: Add reCAPTCHA to Your Views
Add the reCAPTCHA script to your layout file (e.g., layouts/auth.blade.php
):
<!DOCTYPE html>
<html>
<head>
<!-- Other head elements -->
<script src="https://www.google.com/recaptcha/enterprise.js" async defer></script>
</head>
<body>
@yield('content')
</body>
</html>
Add the reCAPTCHA widget to your login form (login.blade.php
):
<form method="POST" action="{{ route('login') }}">
@csrf
<!-- Email and password fields -->
<div class="form-group mt-4">
@error('g-recaptcha-response')
<span class="text-danger">{{ $message }}</span>
@enderror
<div class="g-recaptcha" data-sitekey="{{ config('captcha.sitekey') }}" data-action="LOGIN"></div>
</div>
<button type="submit" class="btn btn-primary">
{{ trans('menu.login') }}
</button>
</form>
And to your registration form (register.blade.php
):
<form method="POST" action="{{ route('register') }}">
@csrf
<!-- Name, email, and password fields -->
<div class="form-group mt-4">
@error('g-recaptcha-response')
<span class="text-danger">{{ $message }}</span>
@enderror
<div class="g-recaptcha" data-sitekey="{{ config('captcha.sitekey') }}" data-action="REGISTER"></div>
</div>
<button type="submit" class="btn btn-primary">
{{ trans('menu.sign_up') }}
</button>
</form>
Step 4: Update Controllers
Modify your LoginController.php
:
use App\Traits\RecaptchaValidation;
class LoginController extends Controller
{
use RecaptchaValidation;
public function login(Request $request)
{
// Validate login credentials and reCAPTCHA token
$this->validateLogin($request);
$request->validate([
'g-recaptcha-response' => 'required|string',
]);
// Validate reCAPTCHA
$this->validateRecaptchaOrFail(
$request->input('g-recaptcha-response'),
'LOGIN'
);
// Continue with login logic
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
}
And your RegisterController.php
:
use App\Traits\RecaptchaValidation;
class RegisterController extends Controller
{
use RecaptchaValidation;
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'g-recaptcha-response' => ['required', 'string'],
]);
}
public function register(Request $request)
{
$this->validator($request->all())->validate();
// Validate reCAPTCHA before registration
$this->validateRecaptchaOrFail(
$request->input('g-recaptcha-response'),
'REGISTER'
);
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
}
Et voila! You're welcome.
Any thoughts or feedback on this?
Let me know in the comments.
Top comments (0)