Introduction
Verifying Google reCAPTCHA / Cloudflare Turnstile responses
Two classes verify the token posted by a CAPTCHA widget with the service's API:
Fegg\Captcha\ReCAPTCHA: Google reCAPTCHA v3 (score based)Fegg\Captcha\Turnstile: Cloudflare Turnstile
Both send the token with Fegg\Http\Client (cURL when it is installed, otherwise PHP's http stream wrapper; 5 second timeout, 3 seconds to connect).
When the service cannot be reached (timeout, outage, an answer that is not HTTP 200 JSON), auth() fails: success is false and errors has a message asking the visitor to try again later (確認サービスに接続できませんでした。…). A warning is written to the application log. A form protected by a captcha is never accepted unchecked.
How to use
Import the class and create it with new, passing the secret key. Then pass the posted token to auth().
<?php
namespace Site\Controller;
defined('FEGG_CORE_DIR') || exit;
use Fegg\Captcha\ReCAPTCHA;
use Fegg\Captcha\Turnstile;
use Fegg\Validation;
class Entry extends Controller
{
public function confirm()
{
$validation = new Validation();
// reCAPTCHA (the secret comes from site/config/env.php)
$recaptcha = new ReCAPTCHA(feggEnv('FEGG_RECAPTCHA_SECRET', ''), 0.5);
$result = $recaptcha->auth($this->in('g-recaptcha-response'));
if (!$result['success']) {
$validation->setError('g-recaptcha-response', implode(',', $result['errors']));
}
// Cloudflare Turnstile
$turnstile = new Turnstile(feggEnv('FEGG_TURNSTILE_SECRET', ''));
$result = $turnstile->auth($this->in('cf-turnstile-response'));
if (!$result['success']) {
$validation->setError('cf-turnstile-response', implode(',', $result['errors']));
}
}
}
Keys
The keys differ per server, and the secret keys must not be committed. Put them in site/config/env.php (or environment variables) and read them with feggEnv():
'FEGG_RECAPTCHA_SITE_KEY' => '...',
'FEGG_RECAPTCHA_SECRET' => '...',
'FEGG_TURNSTILE_SITE_KEY' => '...',
'FEGG_TURNSTILE_SECRET' => '...',
Use different keys (or the providers' test keys) for development and production. On a test server the keys can go in the committed site/config/env/staging.php (see Test servers deployed with git pull):
| Site key | Secret | |
|---|---|---|
| Turnstile, always passes | 1x00000000000000000000AA | 1x0000000000000000000000000000000AA |
| Turnstile, always fails | 2x00000000000000000000AB | 2x0000000000000000000000000000000AA |
Test secrets accept only the dummy token of the test site keys (Cloudflare); never use them in production. reCAPTCHA v3 has no test keys: register the test server's domain, and set FEGG_RECAPTCHA_SCORE_THRESHOLD to 1.0 to see a rejection. When a captcha is enabled but a key is empty, every form is rejected and an error is logged.
Contact form
The contact form of the skeleton supports both. Enable one in htdocs/_fegg/site/config/contact/contact_config.php with $_enable_recaptcha or $_enable_turnstile; the file reads the keys from feggEnv():
$_enable_recaptcha = true;
$_recaptcha_key = feggEnv('FEGG_RECAPTCHA_SITE_KEY', '');
$_recaptcha_secret = feggEnv('FEGG_RECAPTCHA_SECRET', '');
$_recaptcha_score_threshold = 0.5;
The widgets are output by site/template/template/antispam.tpl, and site/controller/Contact.php verifies them. Projects whose old contact_config.php has the keys written in it keep working; move them to env.php when convenient.