laravel-captcha maintained by eii
Eii Laravel CAPTCHA
A configurable CAPTCHA integration for Laravel with Cloudflare Turnstile and Google reCAPTCHA adapters.
Development status: initial scaffold. Review and test the frontend widget lifecycle and Livewire integration before using this package in production. The package is intentionally fail-closed during server verification, but a scaffold is not a security audit.
Requirements
- PHP 8.2+
- Laravel 11, 12, or 13
- Livewire is optional; install a compatible Livewire version if you want the Livewire component.
Install
composer require eii/laravel-captcha
php artisan vendor:publish --tag=eii-captcha-config
Configure .env:
EII_CAPTCHA_ENABLED=true
EII_CAPTCHA_PROVIDER=turnstile
EII_CAPTCHA_VERIFY_HOSTNAME=false
EII_CAPTCHA_STRICT=false
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
# Or use Google reCAPTCHA:
# EII_CAPTCHA_PROVIDER=recaptcha
# RECAPTCHA_VERSION=v2
# RECAPTCHA_SITE_KEY=
# RECAPTCHA_SECRET_KEY=
# RECAPTCHA_SCORE_THRESHOLD=0.5
Optional defense-in-depth settings:
# Secondary hostname verification in your Laravel app (off by default)
EII_CAPTCHA_VERIFY_HOSTNAME=false
EII_CAPTCHA_ALLOWED_HOSTNAMES=example.com,www.example.com
# Optional success-event logging for audit trails
EII_CAPTCHA_LOG_SUCCESS=false
Do not use test credentials in production. Use provider-documented test keys or HTTP fakes in local development and automated tests. Never disable server-side verification merely because APP_ENV=local.
Blade
<form method="POST" action="{{ route('contact.store') }}">
@csrf
<input name="email" type="email" required>
<x-eii-captcha name="captcha" />
<button type="submit">Send</button>
</form>
Include a scripts stack in your layout:
@stack('scripts')
Controller:
use Eii\Captcha\Rules\CaptchaRule;
use Illuminate\Http\Request;
public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
'captcha' => ['required', new CaptchaRule()],
]);
// Continue processing only after validation succeeds.
}
Manual verification
use Eii\Captcha\Facades\Captcha;
if (! Captcha::verify($request->input('captcha'), $request->ip())) {
abort(422, 'CAPTCHA verification failed.');
}
Livewire
If Livewire is installed, a component is registered as eii-captcha-widget. Example:
<form wire:submit="save">
<input type="email" wire:model="email">
<livewire:eii-captcha-widget field="captcha" />
<button type="submit">Send</button>
</form>
In the Livewire component, validate the submitted token on the server with the CaptchaManager or CaptchaRule; do not trust a client-side success event alone. This integration is an initial scaffold and needs browser-level tests and lifecycle hardening before production use.
Diagnostic command
php artisan eii-captcha:check
The command checks common local configuration errors. It cannot inspect allowed hostnames or keys configured in the Google or Cloudflare dashboards. For production, configure a hostname allowlist and enable EII_CAPTCHA_VERIFY_HOSTNAME=true.
To use hostname verification, add this to the published config (if you prefer config over env):
'verify_hostname' => true,
'allowed_hostnames' => ['example.com', 'www.example.com'],
Security notes
- Provider secrets are server-side only.
- Verification failures and provider outages fail closed.
- Do not trust a client-side widget's success state.
- Use separate development and production provider credentials.
- Configure allowed hostnames in both the provider dashboard and this package config when
verify_hostnameis enabled. - A localhost domain in a provider dashboard does not itself bypass server verification; accidentally accepting unverified tokens does.
- Verify reCAPTCHA v3 score and expected action for each protected operation.
- Review and test multiple widgets, Livewire DOM morphing, expired tokens, retries, CSP, and script loading before production deployment.