Developed with love by KnpLabs Hire us for your project!
45

KarserRecaptcha3Bundle

by karser

Google ReCAPTCHA v3 for Symfony

KarserRecaptcha3Bundle

Build Status
Scrutinizer Code Quality
Code Coverage
Total Downloads

reCAPTCHA v3 returns a score for each request without user friction.
The score is based on interactions with your site (1.0 is very likely a good interaction,
0.0 is very likely a bot) and enables you to
take an appropriate action for your site. Register reCAPTCHA v3 keys
here.

image

Installation

With composer, require:

composer require karser/karser-recaptcha3-bundle

You can quickly configure this bundle by using symfony/flex:
- answer no for google/recaptcha
- answer yes for karser/karser-recaptcha3-bundle
image

Configuration without symfony/flex:

1. Register the bundle

Symfony 4/5 Version :

Register bundle into config/bundles.php:
php
return [
//...
Karser\Recaptcha3Bundle\KarserRecaptcha3Bundle::class => ['all' => true],
];

Symfony 3 Version:

Register bundle into app/AppKernel.php:

public function registerBundles()
{
    return array(
        // ...
        new Karser\Recaptcha3Bundle\KarserRecaptcha3Bundle(),
    );
}

2. Add configuration files

# config/packages/karser_recaptcha3.yaml (or app/config/config.yml if using Symfony3)
karser_recaptcha3:
    site_key: '%env(RECAPTCHA3_KEY)%'
    secret_key: '%env(RECAPTCHA3_SECRET)%'
    score_threshold: 0.5

Add your site key and secret to your .env file:
```

> karser/recaptcha3-bundle

RECAPTCHA3_KEY=my_site_key
RECAPTCHA3_SECRET=my_secret

< karser/recaptcha3-bundle


Usage
-----

### How to integrate re-captcha in Symfony form:

```php
<?php

use Karser\Recaptcha3Bundle\Form\Recaptcha3Type;
use Karser\Recaptcha3Bundle\Validator\Constraints\Recaptcha3;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('captcha', Recaptcha3Type::class, [
            'constraints' => new Recaptcha3(),
            'action_name' => 'homepage',
            'script_nonce_csp' => $nonceCSP,
        ]);
        //$builder->add(...);
    }
}

Notes:
- The action_name parameter is reCAPTCHA v3 action which identifies the submission of this particular form in the Google reCAPTCHA dashboard, and confirming it is as expected in the backend is a recommended extra security step.
- The script_nonce_csp parameter is optional. You must use the same nonce as in your Content-Security Policy header.

How to integrate re-captcha in API method:

The idea is to require the frontend to submit the captcha token, so it will be validated on server side.

First you need to add the captcha field to your transport entity:
```php
<?php

namespace App\Dto;

final class UserSignupRequest
{
/** @var string|null */
public $email;

/** @var string|null */
public $captcha;

}
```

And to add the validation constraint:

#config/validator/validation.yaml
App\Dto\UserSignupRequest:
    properties:
        email:
            - NotBlank: ~
            - Email: { mode: strict }
        captcha:
            - Karser\Recaptcha3Bundle\Validator\Constraints\Recaptcha3: ~

On frontend part you need to submit the captcha token along with email.
You can obtain the captcha token either on page load or on form submit.

<script src="https://www.google.com/recaptcha/api.js?render=<siteKey>"></script>

<script>
const siteKey = '*****************-**-******-******';

//either on page load
grecaptcha.ready(function() {
    grecaptcha.execute(siteKey, {
        action: 'homepage'
    }).then(function(token) {
        //the token will be sent on form submit
        $('[name="captcha"]').val(token);
        //keep in mind that token expires in 120 seconds so it's better to add setTimeout.
    });
});

//or on form post:
grecaptcha.ready(function() {
    grecaptcha.execute(siteKey, {
        action: 'homepage'
    }).then(function(token) {
        //submit the form
        return http.post(url, {email, captcha: token});
    });
});
</script>

How to deal with functional and e2e testing:

Recaptcha won't allow you to test your app efficiently unless you disable it for the environment you are testing against.

# app/config/config.yml (or config/packages/karser_recaptcha3.yaml if using Symfony4)
karser_recaptcha3:
    enabled: '%env(bool:RECAPTCHA3_ENABLED)%'
#.env.test or an environment variable
RECAPTCHA3_ENABLED=0

How to set the threshold from PHP dynamically rather from the .yaml config or .env?

You should inject @karser_recaptcha3.google.recaptcha in your service and call setScoreThreshold method.
```yaml

services.yaml

App\Services\YourService:
arguments: ['@karser_recaptcha3.google.recaptcha']
```

#App/Services/YourService.php

use ReCaptcha\ReCaptcha;

class YourService {
    private $reCaptcha;

    public function __construct(ReCaptcha $reCaptcha) {
        $this->reCaptcha = $reCaptcha;
    }

    public function yourMethod() {
        $this->reCaptcha->setScoreThreshold(0.7);
    }
}

How to resolve IP propertly when behind Cloudflare:

From the Cloudflare docs:
To provide the client (visitor) IP address for every request to the origin, Cloudflare adds the CF-Connecting-IP header.

"CF-Connecting-IP: A.B.C.D"

So you can implement custom IP resolver which attempts to read the CF-Connecting-IP header or fallbacks with the internal IP resolver:

<?php declare(strict_types=1);

namespace App\Service;

use Karser\Recaptcha3Bundle\Services\IpResolverInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class CloudflareIpResolver implements IpResolverInterface
{
    /** @var IpResolverInterface */
    private $decorated;

    /** @var RequestStack */
    private $requestStack;

    public function __construct(IpResolverInterface $decorated, RequestStack $requestStack)
    {
        $this->decorated = $decorated;
        $this->requestStack = $requestStack;
    }

    public function resolveIp(): ?string
    {
        return $this->doResolveIp() ?? $this->decorated->resolveIp();
    }

    private function doResolveIp(): ?string
    {
        $request = $this->requestStack->getCurrentRequest();
        if ($request === null) {
            return null;
        }
        return $request->server->get('HTTP_CF_CONNECTING_IP');
    }
}

Here is the service declaration. It decorates the internal resolver:
```yaml

services.yaml

services:
App\Service\CloudflareIpResolver:
decorates: 'karser_recaptcha3.ip_resolver'
arguments:
$decorated: '@App\Service\CloudflareIpResolver.inner'
$requestStack: '@request_stack'
```

Troubleshooting checklist

Make sure you setup recaptcha key/secret of version 3.

Also, make sure you added the domain you use in the recaptcha settings.
Usually dev domain differs from the production one, so better to double check.
image

Make sure you are seeing this in the html of your rendered form

<input type="hidden" id="form_captcha" name="form[captcha]" /><script>
    var recaptchaCallback_form_captcha = function() {
    grecaptcha.execute('<YOUR-RECAPTCHA-KEY>', {action: 'landing'}).then(function(token) {
    document.getElementById('form_captcha').value = token;
    });
    };
    </script><script src="https://www.google.com/recaptcha/api.js?render=<YOUR-RECAPTCHA-KEY>&onload=recaptchaCallback_form_captcha" async defer></script> 
</form>

Make sure you don't have javascript errors in the browser console

Testing

composer update
vendor/bin/phpunit
MIT License

Copyright (c) 2019 Dmitrii Poddubnyi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
  • Added default config for the new parameter "script_nonce_csp" in unit tests
    By karser, 3 months ago
  • Added support for Content-Security Policy nonce
    By karser, 3 months ago
  • Added FAQ How to set the threshold from PHP dynamically
    By karser, 4 months ago
  • Added troubleshooting checklist
    By karser, 4 months ago
  • Added symfony 5.1 compatibility
    By karser, 5 months ago
  • Made flex installation tips more readable
    By web-flow, 6 months ago
  • Twig configuration fix
    By karser, 6 months ago
  • Improved readme
    By web-flow, 6 months ago
  • Refresh token before expiration (#13)
    By web-flow, 7 months ago
  • Fixed a bug where validation was passing if value is null or undefined
    By karser, 7 months ago
  • Documented installation with flex
    By web-flow, 8 months ago
  • prioritize symfony 4/5
    By web-flow, 8 months ago
  • Documented to avoid executing unwanted flex recipe
    By web-flow, 8 months ago
  • Fixed typo
    By karser, 9 months ago
  • Documented how to add env vars
    By karser, 9 months ago
  • Added sf5 compat (#8)
    By web-flow, 9 months ago
  • README: fixed env variable casting (#6)
    By karser, 11 months ago
  • Temporary disable dev-stable build
    By karser, 1 year ago
  • Documented action_name #5
    By karser, 1 year ago
  • Defer + Async Recaptcha JS (#4)
    By karser, 1 year ago
  • Merge pull request #3 from karser/little-issues-fix
    By web-flow, 1 year ago
  • Cover validator and form with unit tests
    By karser, 1 year ago
  • Added internal IP resolver and Cloudflare example
    By karser, 1 year ago
  • Removed default for enabled
    By karser, 1 year ago
  • Added functional testing docs
    By web-flow, 1 year ago
  • Added api integration docs
    By karser, 1 year ago
  • Replaced the image
    By karser, 1 year ago
  • Fixed template format
    By web-flow, 1 year ago
  • added sf4 docs
    By web-flow, 1 year ago
  • added an image from analytics dashboard
    By web-flow, 1 year ago