beelab/recaptcha2-bundle
BeelabRecaptcha2Bundle integrates Google ReCaptcha v2 into Symfony, adding a ready-to-use form type and server-side verification with configurable API keys. Simple to drop into existing forms for spam protection. Released under the LGPL license.
Install via Composer (with Symfony dependencies):
composer require beelab/recaptcha2-bundle symfony/form symfony/validator symfony/twig-bridge
Note: Requires Symfony components due to bundle design.
Create a Laravel Service Provider (app/Providers/RecaptchaServiceProvider.php):
use Beelab\Recaptcha2Bundle\BeelabRecaptcha2Bundle;
use Symfony\Component\HttpKernel\KernelInterface;
class RecaptchaServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(KernelInterface::class, function () {
return new class implements KernelInterface {
public function getProjectDir() { return base_path(); }
public function getRootDir() { return base_path(); }
};
});
$this->app->register(BeelabRecaptcha2Bundle::class);
}
}
Configure .env:
RECAPTCHA_SITE_KEY=your_site_key
RECAPTCHA_SECRET_KEY=your_secret_key
Basic Form Validation (Laravel-style):
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'g-recaptcha-response' => [
'required',
function ($attribute, $value, $fail) {
$response = Http::post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.recaptcha.secret'),
'response' => $value,
])->json();
if (!$response['success']) {
$fail('reCAPTCHA verification failed.');
}
},
],
]);
if ($validator->fails()) {
return back()->withErrors($validator);
}
}
Render the Widget (Blade):
<div class="g-recaptcha" data-sitekey="{{ config('services.recaptcha.site_key') }}"></div>
@vite(['resources/js/recaptcha.js']) <!-- Load Google API -->
// app/Providers/AppServiceProvider.php
use Beelab\Recaptcha2Bundle\Form\Type\RecaptchaType;
use Symfony\Component\Form\FormBuilderInterface;
public function boot()
{
FormBuilderInterface::addType(RecaptchaType::class);
}
Limitation: Requires Symfony Form Component; not native to Laravel.
// Custom Rule (app/Rules/Recaptcha.php)
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Http;
class Recaptcha implements Rule
{
public function passes($attribute, $value)
{
$response = Http::post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.recaptcha.secret'),
'response' => $value,
])->json();
return $response['success'] ?? false;
}
public function message()
{
return 'The :attribute is invalid.';
}
}
Usage:
$request->validate([
'recaptcha' => ['required', new Recaptcha],
]);
.envSymfony Config (config/packages/beelab_recaptcha2.yaml) |
Laravel .env Equivalent |
|---|---|
site_key: %env(RECAPTCHA_SITE_KEY)% |
RECAPTCHA_SITE_KEY=... |
secret_key: %env(RECAPTCHA_SECRET_KEY)% |
RECAPTCHA_SECRET_KEY=... |
verify_ssl: true |
RECAPTCHA_VERIFY_SSL=true |
error_message: 'Invalid reCAPTCHA.' |
RECAPTCHA_ERROR_MESSAGE=Invalid reCAPTCHA. |
config/recaptcha.php)return [
'site_key' => env('RECAPTCHA_SITE_KEY'),
'secret_key' => env('RECAPTCHA_SECRET_KEY'),
'verify_ssl' => env('RECAPTCHA_VERIFY_SSL', true),
'error_message' => env('RECAPTCHA_ERROR_MESSAGE', 'Invalid reCAPTCHA.'),
'test_mode' => env('RECAPTCHA_TEST_MODE', false),
];
// src/EventListener/RecaptchaListener.php
use Beelab\Recaptcha2Bundle\Event\RecaptchaFailEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RecaptchaListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [RecaptchaFailEvent::NAME => 'onRecaptchaFail'];
}
public function onRecaptchaFail(RecaptchaFailEvent $event)
{
// Custom logic
}
}
// app/Listeners/RecaptchaFailListener.php
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class RecaptchaFailListener
{
public function handle($event)
{
// $event->data contains failure details
logger()->warning('reCAPTCHA failed', $event->data);
}
}
Register in EventServiceProvider:
protected $listen = [
'recaptcha.failed' => [
RecaptchaFailListener::class,
],
];
# config/packages/test/beelab_recaptcha2.yaml
beelab_recaptcha2:
test_mode: true
// tests/TestCase.php
protected function assertRecaptchaPasses($responseToken)
{
config(['recaptcha.test_mode' => true]);
$this->assertTrue(
Http::post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('recaptcha.secret_key'),
'response' => $responseToken,
])->json()['success']
);
}
Symfony Dependency Bloat
symfony/form, symfony/validator, and symfony/twig-bridge, which may conflict with Laravel’s DI container.composer require symfony/* --ignore-platform-req and manually resolve conflicts.Twig Integration Issues
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="{{ config('recaptcha.site_key') }}"></div>
@vite(['resources/js/recaptcha.js']) to load the API dynamically.Validation Timing
Recaptcha2 constraint validates after form submission. Laravel’s FormRequest validates before.FormRequest::withValidator().Secret Key Exposure
config/cache may expose them.AppServiceProvider::boot():
\Log::setOutput(function ($level, $message) {
return preg_replace('/RECAPTCHA_SECRET_KEY=[^&]+/', 'RECAPTCHA_SECRET_KEY=[REDACTED]', $message);
});
Test Mode Conflicts
test_mode bypasses validation entirely. Laravel’s testing requires manual mocking:
Http::fake([
'https://www.google.com/recaptcha/api/siteverify' => Http::response(['success' => true]),
]);
Verify API Responses
$response = Http::post('...')->throw()->json();
logger()->debug('reCAPTCHA response', $response);
Check IP/Network Restrictions
Http::withOptions(['proxy' => 'http://proxy.example.com:8080'])->post('...');
Validate Site Key
data-sitekey matches your Google Console settings. Test with:
curl "https://www.google.com/recaptcha/api/siteverify?secret=YOUR_SECRET&response=TOK
How can I help you explore Laravel packages today?