Search by

therealworld / formrecaptchaenterprise-module

therealworld

A Google-Recaptcha-Enterprise-Module for public forms like contact-form in OXID7.

Package info

bitbucket.org/therealworld/formrecaptchaenterprise-module/

Homepage

Type:oxideshop-module

pkg:composer/therealworld/formrecaptchaenterprise-module

Statistics

Installs: 61

Dependents: 0

Suggesters: 0

v2.0.20 2026-10-25 12:18 UTC

README

Deutsche Fassung: README.de.md

vendor-logo the-real-world.de

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 of therealworld/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 id trwcaptcha) 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)SettingDefaultEffect
Google Cloud Project IDsTRWFormReCaptchaEnterpriseProjectIdemptyproject ID (not the name), part of the API URL
API Key for Enterprise APIsTRWFormReCaptchaEnterpriseApiKeyemptyAPI key used for the assessment call
Website Key (Site Key)sTRWFormReCaptchaEnterpriseSiteKeyemptypublic site key; loads enterprise.js and is sent with the assessment
Enable advanced risk analysisbTRWFormReCaptchaEnterpriseCheckRiskReasonsoffadditionally checks Google's risk reasons against the list below
Blocked risk reasonsaTRWFormReCaptchaEnterpriseBlockedRiskReasonsemptyone reason per line, e.g. AUTOMATION, UNEXPECTED_ENVIRONMENT, TOO_MUCH_TRAFFIC; only used when the switch above is on
Spam ScoresTRWFormReCaptchaEnterpriseScore0.5threshold 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)SettingDefaultEffect
use Google ReCaptcha Enterprise ProtectionbTRWFormReCaptchaEnterpriseUseReCaptchaEnterpriseoffmaster switch; nothing is rendered or checked while it is off
Show in the frontend an error message ...bTRWFormReCaptchaEnterpriseThrowMessageOnErroroffshows "The form was misused and therefore not sent." on a rejected submission
Create an error log entry if someone has fallen into the recaptcha-enterprisebTRWFormReCaptchaEnterpriseThrowExceptionOnErroronwrites an error entry for every rejected submission
Should the IP be logged?bTRWFormReCaptchaEnterpriseCollectIPoffadds 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 formbTRWFormReCaptchaEnterpriseWriteLogForUser, ...Newsletter, ...Contact, ...Details, ...Passwordoffwrites an info entry for every submission of that form group, passed or rejected

Integrations

Option (admin)SettingDefaultCovers
Add to register forms?bTRWFormReCaptchaEnterpriseIntegrationUseronregistration, invitation
Add to newsletter form?bTRWFormReCaptchaEnterpriseIntegrationNewsletteronnewsletter
Add to conctact form?bTRWFormReCaptchaEnterpriseIntegrationContactoncontact
Add to detail page forms (pricealarm)?bTRWFormReCaptchaEnterpriseIntegrationDetailsonprice alarm
Add to forgot password form?bTRWFormReCaptchaEnterpriseIntegrationPasswordonforgot password
Add to forms of other modules?bTRWFormReCaptchaEnterpriseIntegrationExternalFormsonall 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 Error part of the log.
  • If enterprise.js is 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_captcha and forgotpwd_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) returns true if the submission is to be rejected; getExpectedActionForController() resolves the action.
  • FormGuard: Service\FormGuard decorates TheRealWorld\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.