karser/karser-recaptcha3-bundle
Install the Bundle
composer require karser/karser-recaptcha3-bundle
Enable the bundle in config/bundles.php:
Karser\Recaptcha3Bundle\KarserRecaptcha3Bundle::class => ['all' => true],
Configure Keys
Add your Google reCAPTCHA v3 keys to .env:
KARSER_RECAPTCHA_SITE_KEY=your_site_key
KARSER_RECAPTCHA_SECRET_KEY=your_secret_key
First Use Case: Validate a Form Submission Inject the validator into a controller and use it in a form handler:
use Karser\Recaptcha3Bundle\Validator\Constraints\Recaptcha3;
use Symfony\Component\Validator\Validator\ValidatorInterface;
#[Route('/contact', name: 'contact')]
public function contact(Request $request, ValidatorInterface $validator): Response
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$constraint = new Recaptcha3();
$errors = $validator->validate($form->getData(), $constraint);
if (count($errors) === 0) {
// Proceed with submission
}
}
return $this->render('contact.html.twig', ['form' => $form->createView()]);
}
Add reCAPTCHA to a Form
Use the Recaptcha3 constraint in your form type:
use Karser\Recaptcha3Bundle\Validator\Constraints\Recaptcha3;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('email')
->add('message')
->add('recaptcha', Recaptcha3::class, [
'mapped' => false,
'threshold' => 0.5, // Optional: Default is 0.5
]);
}
Render the reCAPTCHA Widget In your Twig template:
{{ form_widget(form.recaptcha) }}
Adjust thresholds based on user behavior or risk levels:
// In a controller or service
$constraint = new Recaptcha3(['threshold' => $dynamicThreshold]);
Validate reCAPTCHA scores in API requests using a custom validator:
use Karser\Recaptcha3Bundle\Validator\Recaptcha3Validator;
public function validateRecaptcha(Request $request, Recaptcha3Validator $validator)
{
$score = $validator->validateToken($request->request->get('g-recaptcha-response'));
if ($score < 0.5) {
throw new \RuntimeException('reCAPTCHA validation failed');
}
return $score;
}
Use Symfony events to validate reCAPTCHA before form submission:
// src/EventListener/RecaptchaListener.php
use Karser\Recaptcha3Bundle\Validator\Recaptcha3Validator;
use Symfony\Component\HttpKernel\Event\RequestEvent;
class RecaptchaListener
{
public function __construct(private Recaptcha3Validator $validator) {}
public function onKernelRequest(RequestEvent $event)
{
if ($event->isMainRequest() && $event->getRequest()->isXmlHttpRequest()) {
$score = $this->validator->validateToken($event->getRequest()->request->get('g-recaptcha-response'));
if ($score < 0.3) {
$event->setResponse(new JsonResponse(['error' => 'reCAPTCHA failed'], 403));
}
}
}
}
Create a custom voter to block high-risk submissions:
use Karser\Recaptcha3Bundle\Validator\Recaptcha3Validator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class RecaptchaVoter extends Voter
{
public function __construct(private Recaptcha3Validator $validator) {}
protected function supports(string $attribute, $subject): bool
{
return $attribute === 'RECAPTCHA_VALID';
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$score = $this->validator->validateToken($subject->getRecaptchaToken());
return $score >= 0.5;
}
}
Validate reCAPTCHA scores in bulk (e.g., for imported data):
use Karser\Recaptcha3Bundle\Validator\Recaptcha3Validator;
public function processBatch(array $submissions, Recaptcha3Validator $validator)
{
$validSubmissions = [];
foreach ($submissions as $submission) {
$score = $validator->validateToken($submission['recaptcha_token']);
if ($score >= 0.5) {
$validSubmissions[] = $submission;
}
}
return $validSubmissions;
}
Include the reCAPTCHA script in your base template (base.html.twig):
<script src="https://www.google.com/recaptcha/api.js?render={{ app.env('KARSER_RECAPTCHA_SITE_KEY') }}"></script>
Add a hidden field to your forms:
<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response">
Use JavaScript to auto-fill the token:
grecaptcha.ready(function() {
grecaptcha.execute('{{ app.env('KARSER_RECAPTCHA_SITE_KEY') }}', {action: 'submit'})
.then(function(token) {
document.getElementById('g-recaptcha-response').value = token;
});
});
Override default validation messages in your form type:
$builder->add('recaptcha', Recaptcha3::class, [
'mapped' => false,
'message' => 'This submission looks like it might be automated. Please try again.',
]);
Use the Recaptcha3Validator to mock responses in tests:
use Karser\Recaptcha3Bundle\Validator\Recaptcha3Validator;
public function testRecaptchaValidation(Recaptcha3Validator $validator)
{
$validator->setTestMode(true); // Enable test mode
$validator->setExpectedScore(0.9); // Simulate a high score
$this->assertTrue($validator->validateToken('fake_token'));
}
Token Expiry
grecaptcha.execute() in the form submission handler (e.g., with onsubmit).Threshold Misconfiguration
0.5) may be too lenient for high-risk actions (e.g., password resets).0.9) for sensitive operations:
$constraint = new Recaptcha3(['threshold' => 0.9]);
Missing mapped => false
mapped => false in the form field will cause validation errors.$builder->add('recaptcha', Recaptcha3::class, ['mapped' => false]);
Caching Issues
php bin/console cache:clear
IP-Based Rate Limits
Test Mode Pitfalls
.env has:
KARSER_RECAPTCHA_TEST_MODE=false
use Psr\Log\LoggerInterface;
class CustomRecaptchaValidator extends Recaptcha3Validator
{
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger
How can I help you explore Laravel packages today?