How to implement BotDetect Captcha in your Symfony 4 Forms

We wrote an article long time ago, about how to implement the Google ReCaptcha in your Symfony 3 project, which works fine as long as you can rely on the mentioned tool. However, some applications restrict the usage of Google products. So, the developer will have to implement another option to prevent bots from filling forms randomly on the web. One of the most known libraries to achieves this easily in PHP is with the BotDetect captcha library.

In this article, we will explain you how to install and use the BotDetect Captcha library in Symfony 4.

1. Install BotDetect Captcha

BotDetect CAPTCHA generator is a non-stalking form-security solution that uses a mix of measures, that are easy for humans but hard for bots, to prevent automated form posting. BotDetect also provides an audio Captcha alternative to keep websites accessible to people with impaired vision, enabling you to make WCAG and Section 508 compliant websites.

In Symfony 4, you can easily install the library through composer using the symfony-captcha-bundle with the following command:

composer require captcha-com/symfony-captcha-bundle:"4.*"

This will install the library and will register its classes automatically on your project. For more information about this library, please visit the official website here.

2. Register Captcha Bundle Routes

In order to render the captcha, request audios etc. you will need to register the routes that the bundle already includes in the /config/routes.yaml file like this:

# /app/config/routes.yaml
captcha_routing:
    resource: "@CaptchaBundle/Resources/config/routing.yml"

This will allow the captcha to work in the frontend once it has been implemented in the backend.

3. Create configuration file

Now you will need to create a configuration file in PHP in the config/packages directory of your application. I know, we usually add only yaml files in this directory, however this library is a special case as it has a hardcoded require that includes a PHP file from the mentioned directory, so you will need to add obligatorily a PHP file with a simple if and a return with an array:

<?php 
// app/config/packages/captcha.php
if (!class_exists('CaptchaConfiguration')) { return; }

// BotDetect PHP Captcha configuration options
return [
    // Captcha configuration for example form
    'ExampleCaptchaUserRegistration' => [
        'UserInputID' => 'captchaCode',
        'ImageWidth' => 250,
        'ImageHeight' => 50,
    ],
];

The returned associative array will have as keys the name of the custom configuration of a single field. In this example, we will have only a single captcha configuration that will be used for an User registration form. The idea is to customize every captcha wherever you need it, you can specify which configuration will be used later on the FormType.

4. Add a CaptchaType field to your FormType

Following the Symfony guidelines, when we create a form we should obviously have a FormType associated to some Entity. In this case, as we mentioned previously, we will have a registration form that allows the user to register a new user in the database, this means that the form will persist an User entity in the database. The form contains the regular fields as first name, last name, email and password. However, it should contain as well a new field namely the captcha code, this can be simply added just like the other fields in the buildForm method of the Type.

Note that the field, although isn't on the database, it should be added as a mapped field, just like it looks in the following example:

<?php

// app/src/Form/RegistrationFormType.php
namespace App\Form;

// ... ///

// Import the Captcha Field Type and Validator
use Captcha\Bundle\CaptchaBundle\Form\Type\CaptchaType;
use Captcha\Bundle\CaptchaBundle\Validator\Constraints\ValidCaptcha;

// ... ///

class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ... ///

            ->add('captchaCode', CaptchaType::class, array(
                'captchaConfig' => 'ExampleCaptchaUserRegistration',
                'constraints' => [
                    new ValidCaptcha([
                        'message' => 'Invalid captcha, please try again',
                    ]),
                ],
            ))

            // ... ///
        ;
    }

    // ... ///
}

Theorically, you would be asking to yourself now, that this would make your form fail as we are registering a new field that isn't associated with the User entity, and you're right ! But don't run your app yet, follow the last step and you'll understand why.

5. Add the captchaCode field to your entity

Finally, you will just need to add a new "field" to your entity, that won't be mapped of course, is just need to exist in the entity that you will be trying to persist into the database, so the library can check if the captcha provided by the user was ok or not:

<?php

// src/Entity/User.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

class User
{
    // ... //
    protected $captchaCode;
    
    public function getCaptchaCode()
    {
      return $this->captchaCode;
    }

    public function setCaptchaCode($captchaCode)
    {
      $this->captchaCode = $captchaCode;
    }

    // ... //
}

With this, you should be able now to run your application and you will see the captcha. Don't forget to render the new field in your form view:

{% extends 'base.html.twig' %}

{% block title %}Register{% endblock %}

{% block body %}
    <h1>Register</h1>

    {{ form_start(registrationForm) }}
        {{ form_row(registrationForm.firstName) }}
        {{ form_row(registrationForm.lastName) }}
        {{ form_row(registrationForm.email) }}
        {{ form_row(registrationForm.plainPassword) }}
        {{ form_row(registrationForm.agreeTerms) }}

        {# render the captcha field to the form in the view #}
        {{ form_row(registrationForm.captchaCode) }}

        <button class="btn">Register</button>
    {{ form_end(registrationForm) }}
{% endblock %}

How to hide "BotDetect CAPTCHA Library for Symfony" message

Pitifully, the library doesn't include a way to hide that message as its hardcoded in the library, but you shouldn't modify vendor classes. A workaround to remove this is through JavaScript, once the DOM has loaded:

$(function(){
    $("a[title ~= 'BotDetect']").removeAttr("style");
    $("a[title ~= 'BotDetect']").removeAttr("href");
    $("a[title ~= 'BotDetect']").css('visibility', 'hidden');
});

Happy coding !

This could interest you

Become a more social person