Symfony
EU CAPTCHA integrates with Symfony via the myra-security-gmbh/eu-captcha PHP package. The package ships with EuCaptchaInterface, which you can type-hint in controllers and services so Symfony autowires the client without coupling your code to the concrete class.
Installation
composer require myra-security-gmbh/eu-captcha
Configuration
.env
EUCAPTCHA_SITE_KEY=YOUR_SITEKEY
EUCAPTCHA_SECRET_KEY=YOUR_SECRET
config/services.yaml
Register EuCaptcha as a service and alias the interface to it:
services:
Myrasec\EuCaptcha:
arguments:
$sitekey: '%env(EUCAPTCHA_SITE_KEY)%'
$secret: '%env(EUCAPTCHA_SECRET_KEY)%'
Myrasec\EuCaptchaInterface: '@Myrasec\EuCaptcha'
Symfony resolves the alias automatically, so any service or controller that type-hints EuCaptchaInterface receives the configured EuCaptcha instance.
Controller
<?php
namespace App\Controller;
use Myrasec\EuCaptchaInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ContactController extends AbstractController
{
public function __construct(private EuCaptchaInterface $captcha) {}
#[Route('/contact', name: 'contact', methods: ['POST'])]
public function submit(Request $request): Response
{
$result = $this->captcha->validate(
$request->request->getString('eu-captcha-response'),
$request->getClientIp() ?? '',
);
if (!$result->success()) {
return $this->json(['error' => 'CAPTCHA verification failed'], Response::HTTP_BAD_REQUEST);
}
// process the form...
return $this->json(['status' => 'ok']);
}
}
$request->getClientIp() respects Symfony's trusted proxy configuration, so the real visitor IP is forwarded correctly when running behind a load balancer or CDN. Pass the User-Agent as a third argument to validate() if you want to forward it to the API as well.