therealworld / formrecaptchaenterprise-module
A Google-Recaptcha-Enterprise-Module for public forms like contact-form in OXID7.
Package info
bitbucket.org/therealworld/formrecaptchaenterprise-module/
Type:oxideshop-module
pkg:composer/therealworld/formrecaptchaenterprise-module
Requires
- php: ^8.3
- ext-json: *
- oxid-esales/oxideshop-ce: >=v7.4
- therealworld/tools-plugin: >=v3.3
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-25 12:21:59 UTC
README
Deutsche Fassung: README.de.md

description
Protects the shop's public forms with an invisible (score-based) Google reCAPTCHA Enterprise key.
The visitor sees no challenge: the page loads Google's enterprise.js, which puts a token into a
hidden field of the form. On submit the module creates an assessment for that token through the
reCAPTCHA Enterprise REST API and rejects the submission if the token is invalid, belongs to a
different action, the score is too low or (optionally) Google reports a blocked risk reason.
Protected forms (each can be switched on/off separately):
- registration (
UserComponent::createUser()) and invitation (InviteController::send()) - newsletter subscription (
NewsletterController::send()) - contact form (
ContactController::send()) - price alarm on the detail page and on the price alarm page (
ArticleDetailsController::addMe(),PriceAlarmController::addme()) - forgot password (
ForgotPasswordController::forgotPassword()) - forms of other modules that use the FormGuard contract of
therealworld/tools-plugin(for example the withdrawal form oftherealworld/withdrawal-module)
A rejected submission is simply not carried out: no mail, no account, no newsletter subscription, no price alarm.
Alternative without Google:
therealworld/captcha-module(module idtrwcaptcha) protects the same forms with local checks only (honeypot, dwell time, proof of work, image challenge) and needs no keys and no external API. It plugs into the same FormGuard contract.
how it talks to Google
No Google SDK is used (the google/cloud-recaptcha-enterprise require was removed in v2.0.19, it
was never used). The module sends one HTTP request per protected submission with Guzzle (through
ToolsApi of tools-plugin), timeout 10 seconds:
POST https://recaptchaenterprise.googleapis.com/v1/projects/<Project ID>/assessments?key=<API key>
{"event": {"token": ..., "siteKey": ..., "expectedAction": ..., "userAgent": ..., "userIpAddress": ...}}
Authentication is the API key only; no service account or credentials file is needed. The visitor's user agent and IP address are always sent to Google as part of the assessment (independent of the "log the IP" option, which only affects the log).
requirements
- OXID eShop CE
>=v7.4, PHP^8.3,ext-json therealworld/tools-plugin>=v3.3(brings the FormGuard contract this module decorates)- a Google Cloud project with the reCAPTCHA Enterprise API enabled, a score-based website key (site key) and an API key that is allowed to call the reCAPTCHA Enterprise API. Google bills assessments beyond a free quota; check Google's current pricing.
installation
- composer package:
therealworld/formrecaptchaenterprise-module - OXID module id:
trwformrecaptchaenterprise
composer require therealworld/formrecaptchaenterprise-module
vendor/bin/oe-console oe:module:activate trwformrecaptchaenterprise
With therealworld/clirun-plugin installed, vendor/bin/oe-console trw:module:setup trwformrecaptchaenterprise
does the same in one step.
configuration
Admin: Extensions > Modules > the-real-world - Form Google ReCaptcha Enterprise Protection > Settings.
Google Recaptcha Enterprise API
| Option (admin) | Setting | Default | Effect |
|---|---|---|---|
| Google Cloud Project ID | sTRWFormReCaptchaEnterpriseProjectId | empty | project ID (not the name), part of the API URL |
| API Key for Enterprise API | sTRWFormReCaptchaEnterpriseApiKey | empty | API key used for the assessment call |
| Website Key (Site Key) | sTRWFormReCaptchaEnterpriseSiteKey | empty | public site key; loads enterprise.js and is sent with the assessment |
| Enable advanced risk analysis | bTRWFormReCaptchaEnterpriseCheckRiskReasons | off | additionally checks Google's risk reasons against the list below |
| Blocked risk reasons | aTRWFormReCaptchaEnterpriseBlockedRiskReasons | empty | one reason per line, e.g. AUTOMATION, UNEXPECTED_ENVIRONMENT, TOO_MUCH_TRAFFIC; only used when the switch above is on |
| Spam Score | sTRWFormReCaptchaEnterpriseScore | 0.5 | threshold 0.0-1.0; a submission with a score below this value is rejected, a score equal to it passes |
If Project ID, API key or site key is empty, the check is skipped and every submission passes. A warning (!ReCaptcha Enterprise is switched on, but ... is missing) goes to the module log, at most once per request.
Controlling
| Option (admin) | Setting | Default | Effect |
|---|---|---|---|
| use Google ReCaptcha Enterprise Protection | bTRWFormReCaptchaEnterpriseUseReCaptchaEnterprise | off | master switch; nothing is rendered or checked while it is off |
| Show in the frontend an error message ... | bTRWFormReCaptchaEnterpriseThrowMessageOnError | off | shows "The form was misused and therefore not sent." on a rejected submission |
| Create an error log entry if someone has fallen into the recaptcha-enterprise | bTRWFormReCaptchaEnterpriseThrowExceptionOnError | on | writes an error entry for every rejected submission |
| Should the IP be logged? | bTRWFormReCaptchaEnterpriseCollectIP | off | adds the client IP to the log entries; clarify with your data protection officer |
| Generate "Use"-logs for register forms / newsletter form / contact form / detail page forms (pricealarm) / forgot password form | bTRWFormReCaptchaEnterpriseWriteLogForUser, ...Newsletter, ...Contact, ...Details, ...Password | off | writes an info entry for every submission of that form group, passed or rejected |
Integrations
| Option (admin) | Setting | Default | Covers |
|---|---|---|---|
| Add to register forms? | bTRWFormReCaptchaEnterpriseIntegrationUser | on | registration, invitation |
| Add to newsletter form? | bTRWFormReCaptchaEnterpriseIntegrationNewsletter | on | newsletter |
| Add to conctact form? | bTRWFormReCaptchaEnterpriseIntegrationContact | on | contact |
| Add to detail page forms (pricealarm)? | bTRWFormReCaptchaEnterpriseIntegrationDetails | on | price alarm |
| Add to forgot password form? | bTRWFormReCaptchaEnterpriseIntegrationPassword | on | forgot password |
| Add to forms of other modules? | bTRWFormReCaptchaEnterpriseIntegrationExternalForms | on | all forms of other modules registered through the FormGuard contract |
There is also a hidden setting aTRWFormReCaptchaEnterpriseExternalControllers (form key =>
reCAPTCHA action) that other modules fill on activation (the withdrawal-module registers its form
there with the action withdrawal). An entry overrides the "forms of other modules" switch for
that one form; the value off switches it off. It has no field in the admin; to change it by
hand, edit it in var/configuration/shops/<shop-id>/modules/trwformrecaptchaenterprise.yaml and
reactivate the module.
The reCAPTCHA action is the form group: user, newsletter, contact, details, password;
forms of other modules use their registered action or form_submit. The token is created with
the same action that the assessment expects, otherwise Google's token would not match.
logging
Log file: log/trwformrecaptchaenterprise/YYYY-MM-DD.log in the shop's log directory (not the
general OXID log).
!No ReCaptcha token! Data: ...(error) - the hidden field arrived empty (script blocked or form sent before the token was there); always written, the submission is rejected!Form abuse! Data: ...(error) - rejected submission, if the error log switch is on!Form usage! Data: ...(info) - every submission of a form group whose "Use"-log switch is on
Each entry contains the controller, ExpectedAction, the HttpStatus of the API call and
Google's answer: Score, Valid, InvalidReason (e.g. EXPIRED, DUPE, MALFORMED,
BROWSER_ERROR), Action, Reasons, and Error with Google's complete error object (wrong
project, API not enabled, key restricted ...). Then optionally the IP and the submitted form data
(invadr, pa, editval, lgn_usr, or the fields a foreign form hands over).
Note: the "Use"-logs store customers' form input (personal data). They are off by default; switch them on for debugging only and off again afterwards.
limits / known caveats
- If the API call fails or Google answers with an error, the submission is rejected.
- The call is made from the server. An API key restricted to HTTP referrers is refused by Google;
the reason shows up in the
Errorpart of the log. - If
enterprise.jsis blocked (ad blocker, consent tool, CSP), the token field stays empty and the submission is rejected (!No ReCaptcha token!). - The markup is added through the Twig blocks
captcha_form(contact, price alarm, invitation),form_user_billing_country(registration),newsletter_form_captchaandforgotpwd_email_form. A theme without these blocks gets no token and therefore rejects the forms.
for developers
- Chain extensions:
ViewConfig,UserComponent,ArticleDetailsController,ContactController,ForgotPasswordController,InviteController,NewsletterController,PriceAlarmController. - Verification:
Core\FormReCaptchaEnterpriseHelper::checkReCaptchaEnterprise($controller, $writeLog, $formElements)returnstrueif the submission is to be rejected;getExpectedActionForController()resolves the action. - FormGuard:
Service\FormGuarddecoratesTheRealWorld\ToolsPlugin\Contract\FormGuardInterface(decoration_priority: 30) and only handles form keys that are not the shop's own (FormKey::getShopFormKeys()); those are covered by the chain extensions. Results are merged with the inner guard, so several captcha modules can protect the same form. Exceptions while rendering or verifying are logged to the OXID log and let the form pass. - ViewConfig:
integrateReCaptchaEnterprise(),getTRWFormReCaptchaEnterpriseUseReCaptcha(),getTRWFormReCaptchaEnterpriseWebsiteKey(),getTRWFormReCaptchaEnterpriseAction(),getTRWFormReCaptchaEnterpriseActionForController(). - Templates:
@trwformrecaptchaenterprise/frontend/formrecaptchaenterprise.html.twig(shop forms, fills every token field of the same action on the page),@trwformrecaptchaenterprise/frontend/formrecaptchaenterprise_guard.html.twig(foreign forms, plain script tags). The token is refreshed every 60 seconds. - No database tables, no migrations, no CLI commands.