Install the Bundle Run:
composer require aburg/cap-bundle
Then enable it in config/bundles.php:
return [
// ...
Aburg\CapBundle\AburgCapBundle::class => ['all' => true],
];
Configure the Bundle Publish the default config:
php bin/console config:dump-reference Aburg\CapBundle\Configuration > config/packages/cap.yaml
Update config/packages/cap.yaml with your Cap instance URL (e.g., https://trycap.dev/):
aburg_cap:
endpoint: 'https://trycap.dev/'
# Optional: Customize timeout, retry logic, etc.
First Use Case: Protect a Form
Add the CAPTCHA widget to a Symfony form (e.g., src/Form/ContactType.php):
use Aburg\CapBundle\Form\Type\CapType;
$builder->add('captcha', CapType::class, [
'label' => 'Verify you are human',
'mapped' => false, // CAPTCHA is not bound to a model
]);
Render the form template (e.g., templates/contact/index.html.twig):
{{ form_row(form.captcha) }}
Verify Submission In your controller, validate the CAPTCHA response:
use Aburg\CapBundle\Validator\Constraints\ValidCap;
#[ValidCap()]
public function submit(ContactType $form, Request $request) {
// ...
}
Frontend Integration (Symfony UX)
assets/controllers/captcha_controller.js):
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
connect() {
this.element.querySelector('iframe').src = this.data.get('endpoint') + '/widget';
}
}
<div data-controller="captcha" data-captcha-endpoint="{{ aburg_cap.endpoint }}">
{{ form_row(form.captcha) }}
</div>
Backend Validation
ValidCap constraint in Symfony Validator:
use Aburg\CapBundle\Validator\Constraints\ValidCap;
#[ValidCap(message: 'CAPTCHA verification failed.')]
public function handle(Request $request, ContactType $form) {
// ...
}
translations/messages.en.yaml:
aburg_cap:
invalid: 'The CAPTCHA response is invalid.'
API Endpoints
POST:
$builder->add('captcha_token', HiddenType::class, [
'mapped' => false,
]);
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$this->get('aburg_cap.validator')->validate($request->request->get('captcha_token'));
AssetMapper Integration
AssetMapper:
{{ asset('bundles/aburgcap/js/captcha.js') }}
@stimulus import:
{{ stimulus_controller('captcha', { endpoint: aburg_cap.endpoint }) }}
Missing Endpoint Configuration
CapBundle requires a valid endpoint URL.aburg_cap.endpoint in config/packages/cap.yaml. Defaults to null.CSRF Token Conflicts
{{ form_rest(form) }} or manually add:
{{ form_hidden(form._token) }}
Caching CAPTCHA Responses
Cache component to throttle requests:
$cache = $this->get('cache');
if (!$cache->get('cap_verified_' . $userId, false)) {
$this->get('aburg_cap.validator')->validate($token);
$cache->set('cap_verified_' . $userId, true, 3600); // Cache for 1 hour
}
Asset Loading in Production
AssetMapper is misconfigured.config/packages/framework.yaml includes:
assets:
packages:
cap:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
Enable Debug Mode:
Set debug: true in config/packages/cap.yaml to log CAPTCHA requests/responses.
aburg_cap:
debug: true
endpoint: 'https://trycap.dev/'
Manual Validation: Test CAPTCHA responses directly:
$validator = $this->get('aburg_cap.validator');
$isValid = $validator->validate('user_provided_token');
Custom CAPTCHA Widgets
Aburg\CapBundle\Form\Type\CapType:
namespace App\Form\Type;
use Aburg\CapBundle\Form\Type\CapType as BaseCapType;
class CustomCapType extends BaseCapType {
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'widget_attr' => ['class' => 'custom-captcha'],
]);
}
}
services.yaml:
services:
App\Form\Type\CustomCapType:
tags: [form.type]
Alternative CAPTCHA Providers
Aburg\CapBundle\Client\CapClientInterface.Rate Limiting
RateLimiter to block abusive CAPTCHA attempts:
use Symfony\Component\RateLimiter\RateLimiterFactory;
$factory = new RateLimiterFactory(10, 'second');
$limiter = $factory->create($userId);
if (!$limiter->consume()) {
throw $this->createAccessDeniedException('Too many CAPTCHA attempts.');
}
Test Locally: Use a local Cap instance (e.g., Docker) during development to avoid hitting rate limits:
docker run -p 3000:3000 aburg/cap
Then set endpoint: 'http://localhost:3000/'.
Performance: Lazy-load CAPTCHA widgets only when needed (e.g., on form submission):
{% if form.vars.data is not empty %}
{{ form_row(form.captcha) }}
{% endif %}
Accessibility: Ensure CAPTCHA widgets comply with WCAG by adding ARIA labels:
<div {{ form_widget(form.captcha) }} aria-label="Human verification"></div>
How can I help you explore Laravel packages today?