timehunter/laravel-google-recaptcha-v3
Laravel Google reCAPTCHA v3 integration featuring a Blade directive to inject the script, server-side token validation against Google's API, configurable score thresholds, and support for multiple actions. Invisible spam and bot protection with no user interaction.
1. Install
composer require timehunter/laravel-google-recaptcha-v3
2. Publish the config (Laravel auto-discovers the provider on 8+)
php artisan vendor:publish --tag=config
This gives you config/recaptcha.php — this is the first file to open. It holds the site_key, secret_key, and the score/option settings the package passes to Google. Everything else in the package is a thin wrapper around those values.
3. Register keys from the reCAPTCHA v3 admin console (choose v3, not v2). Put them in .env and reference them from the config:
RECAPTCHA_SITE_KEY=6Lc...
RECAPTCHA_SECRET_KEY=6Lc...
4. Render the client script once per page — put this in your base layout, before </body>:
{!! Recaptcha::script() !!}
The site key is public; the secret key must never reach the browser.
5. First real use case: protecting a login or signup form. In the browser you fetch a token for a named action, then POST it:
grecaptcha.ready(() => {
grecaptcha.execute('{{ config('recaptcha.site_key') }}', { action: 'login' })
.then(token => document.querySelector('#recaptcha-token').value = token);
});
On the server, verify before authenticating:
if (! Recaptcha::verify($request->input('recaptcha_token'), 'login')) {
return back()->withErrors(['recaptcha' => 'Verification failed.']);
}
If that round-trip works once, you understand the whole package — v3 is a score endpoint plus plumbing.
Use named actions deliberately. execute(siteKey, { action: 'signup' }) and verify($token, 'signup') must match. Google scopes the score to the action, so reuse one action name across different forms dilutes the signal and lets a token obtained on a low-value page be replayed against a sensitive one. Treat actions as a small enum you own.
Wrap verification in FormRequests. The cleanest day-to-day pattern is a RecaptchaRule or a withValidator hook so controllers stay free of verification noise:
public function rules(): array
{
return [
'email' => 'required|email',
'password' => 'required',
'recaptcha_token' => 'required|string',
];
}
protected function passedValidation(): void
{
abort_unless(Recaptcha::verify($this->recaptcha_token, 'login'), 422);
}
Apply a per-form score threshold. Don't hard-code 0.5 globally. Logins and payments usually want 0.7+ and a follow-up (email code, 2FA); content comments can tolerate 0.3 with moderation. Decide per action, not per app.
Middleware for route groups. If a whole group (e.g. /api/contact/*) needs reCAPTCHA, a small middleware that checks a header or body token keeps the check out of every controller. Keep it off for GET routes and auth-free pages — v3 tokens only make sense when a user action produces one.
Verification happens once, immediately. Because tokens are single-use and short-lived, the pattern is: user clicks → grecaptcha.execute → POST → server verifies synchronously in the same request. Do not queue the verification or store the token for a later job.
Testing. Bind a fake to the facade in your test setup so suites never hit Google:
Recaptcha::shouldReceive('verify')->andReturn(true);
For feature tests that exercise the real controller path with a token field, this is the only realistic option — you cannot mint valid tokens in CI.
Graceful degradation. If Recaptcha::verify throws (network, bad secret, timeout), decide explicitly: on public signup you may fail open with logging; on password reset you should fail closed. Put that decision in your controller or rule, not in the package call.
0.0–1.0) and never blocks by itself. If you need interactive puzzles, that's v2/Enterprise — different keys, different endpoints, incompatible.action: 'submit' and you verify with 'login', the check fails. Log both values during rollout.trustProxies is configured before blaming Google.localhost in many setups — don't infer production behavior from local scores.config/recaptcha.php or .env in a deployed environment, run php artisan config:clear (and restart workers if using Octane). The package reads config at verify time, so stale cache equals stale keys.false on network error and some throw. Wrap in try/catch and log the exception message; Google's failure reasons (timeout-or-duplicate, invalid-input-secret, bad-action) are actionable.{{ Recaptcha::script() }} and are tempted to inline the config array.options array. Anything Google's siteverify accepts (e.g. remoteip) can usually be passed there without touching the package — try that before forking.How can I help you explore Laravel packages today?