Aller au contenu

omnishield/recaptcha

Omnishield reCAPTCHA: Google's captcha - v2 checkbox and invisible, v3 score with its threshold and action, and reCAPTCHA Enterprise assessments - its token checked by direct calls, no Google library.

Étage
Fournisseur · Omnishield
Version
1.x
Suite de conformité
branchée, CI non lue
Vérifié en réel
Vérifié en réel
Licence
MIT
composer require omnishield/recaptcha:1.x-dev

Google reCAPTCHA as an Omnishield captcha, in four modes: v2 checkbox, v2 invisible, v3 score, and Enterprise. The widget is Google's script (https://www.google.com/recaptcha/api.js, or enterprise.js; www.recaptcha.net with the domain option); the token is checked by POST https://<domain>/recaptcha/api/siteverify (v2, v3) or by an assessment, POST https://recaptchaenterprise.googleapis.com/v1/projects/<project_id>/assessments?key=<api_key> (Enterprise). The visitor's browser talks to Google and Google sets the _GRECAPTCHA cookie: load it after consent (privacy).

Installation

composer require omnishield/recaptcha

PHP 8.2 or later, omnisense/omnishield and symfony/http-client (give the factory your application's client, a MockHttpClient in a test).

Credentials

  • v2 and v3: a site key and a secret of a reCAPTCHA key made for the site's domains in Google's reCAPTCHA admin console (a v2 key for checkbox and invisible, a v3 key for score).
  • Enterprise: the site key, the Google Cloud project id and an API key of that project.
  • Test keys: v2 only (below).

The sources

Written from Google's documentation as it read on 2026-10-07: verifying the user's response (siteverify, its answer, its error codes; a token lasts two minutes and serves once), reCAPTCHA v3 (render=<key>, grecaptcha.execute(key, {action}), the score, the 0.5 threshold to start from, the actions' characters), the FAQ (the v2 test keys, the _GRECAPTCHA cookie, www.recaptcha.net, the badge), and for Enterprise creating an assessment and the projects.assessments reference (tokenProperties, invalidReason, riskAnalysis).

The four modes

mode The widget The check Score
checkbox (v2) api.js, <div class="g-recaptcha" data-sitekey>: Google adds g-recaptcha-response siteverify no
invisible (v2) api.js, an element of this package's and a short script: on submit it renders the invisible widget, runs it, and submits once Google gives a token siteverify no
score (v3) api.js?render=<key> and the same script: on submit, grecaptcha.execute(key, {action}) siteverify, then the score against threshold, the action against the one expected yes
enterprise enterprise.js?render=<key>, grecaptcha.enterprise.execute an assessment: POST https://recaptchaenterprise.googleapis.com/v1/projects/<project_id>/assessments?key=<api_key> yes

The script asks for a token at submission, not at page load: a token lasts two minutes. It is printed inline after the element (Widget::$inline): give html() the page's CSP nonce.

Options

Option Type Default Required
mode string checkbox no checkbox, invisible, score, enterprise; another: InvalidConfigException
site_key string - yes
secret string null yes, but for enterprise
project_id, api_key string null yes for enterprise the Google Cloud project, an API key of it
threshold float, 0.0 to 1.0 0.5 no score, enterprise: a score under it is refused (score); outside 0..1: InvalidConfigException
default_action string submit no score, enterprise: the widget's action when the form names none
domain string www.google.com no www.recaptcha.net: the script and siteverify there, where google.com's other cookies are not
language string null no the widget's language (hl)
theme, size string null no checkbox: light, dark (data-theme); normal, compact (data-size)
badge string null no invisible: bottomright, bottomleft, inline (data-badge). To hide v3's badge, Google asks for "This site is protected by reCAPTCHA" in the page
attributes map {} no any other attribute of the element

new RecaptchaGatewayFactory(?HttpClientInterface $http = null): the application's HTTP client. The gateway's title says its mode: Google reCAPTCHA v2 (checkbox), v2 (invisible), v3, Enterprise. Constants: RecaptchaGateway::FIELD (g-recaptcha-response), MODES, TEST_SITE_KEY, TEST_SECRET.

Capabilities

checkbox, invisible score, enterprise
is this token valid (ChallengeInterface) yes yes
a score in the verdict no yes, against threshold
the action checked no yes, when the attempt names one
the host checked yes, when the attempt names one (not with the test keys) the same
third party, cookies Google, _GRECAPTCHA the same
is this content spam, is this identity known no no

The answers

Google answers Verdict / exception
success: true (v2) passed; the host checked when one is expected, but not for the test keys (testkey.google.com)
success: true, score, action (v3) passed when the score reaches the threshold (score) and the action is the one expected (action)
missing-input-response / invalid-input-response / timeout-or-duplicate missing / invalid / spent
missing-input-secret, invalid-input-secret InvalidKeyException
bad-request ProviderException
tokenProperties.valid: false (Enterprise) by invalidReason: MALFORMED, KEY_MISMATCH, BROWSER_ERROR → invalid; EXPIRED → expired; DUPE → duplicate; MISSING → missing; UNEXPECTED_ACTION → action; DOMAIN_MISMATCH → hostname
riskAnalysis (Enterprise) score against the threshold; its reasons (AUTOMATION, UNEXPECTED_ENVIRONMENT...) in the verdict's codes
an error object, HTTP 401/403, PERMISSION_DENIED (Enterprise) InvalidKeyException; another error: ProviderException
no answer, HTTP 5xx, 429 UnreachableException

An empty token is refused at once, never sent: Google's test secret would pass it.

Test keys

v2 only (Google's FAQ): site key 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI, secret 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe - every check passes, from testkey.google.com, and the widget shows a warning. v3 and Enterprise have none: Google advises a key of its own for testing.

Example

A v3 score above and under the threshold, then an Enterprise assessment of a token used twice, Google's answers given by a MockHttpClient (run in omnishield's workspace on 2026-10-26, offline):

<?php

require 'vendor/autoload.php';

use Omnishield\Model\Attempt;
use Omnishield\Recaptcha\RecaptchaGatewayFactory;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

$json = static fn (array $answer) => new MockResponse(json_encode($answer));
$http = new MockHttpClient([
    $json(['success' => true, 'score' => 0.9, 'action' => 'contact', 'challenge_ts' => '2026-10-25T08:00:00Z', 'hostname' => 'www.example.org']),
    $json(['success' => true, 'score' => 0.2, 'action' => 'contact', 'challenge_ts' => '2026-10-25T08:00:00Z', 'hostname' => 'www.example.org']),
    $json(['tokenProperties' => ['valid' => false, 'invalidReason' => 'DUPE'], 'riskAnalysis' => ['score' => 0.0, 'reasons' => []]]),
]);
$v3 = (new RecaptchaGatewayFactory($http))->create(['mode' => 'score', 'site_key' => '6Lc-example', 'secret' => 'example-secret', 'threshold' => 0.5]);
echo $v3->getTitle(), ': ', $v3->widget('contact')->script, "\n";
foreach ([1, 2] as $i) {
    $verdict = $v3->verify(new Attempt('03AFcWeA-token', '192.0.2.10', 'contact'));
    printf("score %.1f: %s\n", $verdict->score, $verdict->passed ? 'passed' : 'refused ('.implode(', ', $verdict->reasons).')');
}
$enterprise = (new RecaptchaGatewayFactory($http))->create(['mode' => 'enterprise', 'site_key' => '6Lc-example', 'project_id' => 'my-project', 'api_key' => 'AIza-example']);
$verdict = $enterprise->verify(new Attempt('03AFcWeA-token', '192.0.2.10', 'contact'));
echo $enterprise->getTitle(), ': refused (', implode(', ', $verdict->reasons), '; ', implode(', ', $verdict->codes), ")\n";
Google reCAPTCHA v3: https://www.google.com/recaptcha/api.js?render=6Lc-example
score 0.9: passed
score 0.2: refused (score)
Google reCAPTCHA Enterprise: refused (duplicate; DUPE)

In Symfony:

omnishield:
    gateways:
        google:
            factory: recaptcha
            options: { mode: score, site_key: '%env(RECAPTCHA_SITE_KEY)%', secret: '%env(RECAPTCHA_SECRET)%', threshold: 0.5 }

Errors

Every failure of Google is an exception whose provider is recaptcha: the table of the answers says which; and at create(), an InvalidConfigException for a key missing for the mode, an unknown mode or a threshold outside 0..1. An empty token is a missing verdict, never sent.

A made-up secret was seen answered invalid-input-response by Google's siteverify (2026-10-07), not invalid-input-secret: a wrong secret then reads as a wrong token (invalid), not as an InvalidKeyException. Check a new secret with a real token before going live.

Verified, and not

The family's conformance suite (Omnishield\Testing\GatewayContractTests) green on 2026-10-25, offline: Tests/RecaptchaContractTest.php on the recorded v2 and written v3 answers; HTTP 401/403 now an InvalidKeyException, siteverify and Enterprise alike
v2 test keys against siteverify done on 2026-10-07 (docker compose run --rm omnishield bare --live, the recorded answers): passes, from testkey.google.com, through www.google.com and www.recaptcha.net alike
A made-up secret seen on 2026-10-07: Google answers invalid-input-response, not invalid-input-secret - a wrong secret looks like a wrong token. Check a new secret with a real token before going live
v2 with a real key, the widget in a browser not verified: no Google account, no browser run
v3 (score) not verified in real: no key - the answers are written from the documentation's fields
Enterprise not verified in real: no project, no key - the assessment and the error are written from the documentation and Google's API error format
The inline script of the invisible and score modes not run in a browser

Responsibility

This package is an independent, unofficial client: it is not affiliated with, endorsed by or sponsored by Google LLC. It reaches their service on behalf of whoever runs it, and it does not redistribute their data beyond the short excerpts recorded as test fixtures. Its MIT licence covers this package's code only and grants no right to the service or to its data.

Whoever installs and runs this package is responsible for complying with reCAPTCHA's terms (Google APIs Terms of Service), for obtaining whatever they require (an account, a key, a licence or subscription, an attribution or a citation, a rate limit to respect), and for any use made of what it returns. The authors publish it free of charge, make no commercial use of it, and accept no liability for how it is used (see the package's LICENSE).

Proposer une modification docs/index.md