Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Recaptcha Enterprise Bundle Laravel Package

codein/recaptcha-enterprise-bundle

A Symfony/PHP bundle that integrates Google reCAPTCHA Enterprise, providing service configuration, form validation, and server-side verification helpers to help protect forms and APIs from bots and spam. Early-stage package with limited adoption and documentation.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire up credentials

composer require codein-labs/recaptcha-enterprise-bundle
php artisan vendor:publish --tag=recaptcha-enterprise-config

Note that the 1.0.0 release notes contain nothing but a changelog link, so the tag names and config keys below are the conventional ones — confirm them against the published config/recaptcha.php and the service provider's publishes() array before you build on them.

You will need three things from the Google Cloud console, and they are not interchangeable:

Value Where it goes Common mistake
Project ID RECAPTCHA_ENTERPRISE_PROJECT_ID Confusing with the project number
Site key Blade / frontend ?render= Using a v2 or v3 key — Enterprise keys are distinct
Service account JSON GOOGLE_APPLICATION_CREDENTIALS Committing it, or breaking relative paths under config:cache

Where to look first

  1. The published config file — it tells you which of the three auth strategies (ADC, explicit JSON path, inline credentials array) the package supports and what the default score threshold is.
  2. The package's validation rule class (typically something like RecaptchaEnterprise) — that is the surface you will touch most.
  3. composer show --tree codein-labs/recaptcha-enterprise-bundle to see whether it wraps google/cloud-recaptcha-enterprise or talks REST directly. That determines how you mock it in tests.

First use case

Protect one low-risk form end-to-end before touching anything else — a contact form or newsletter signup. Render the widget, submit the token, validate it server-side, log the score. Once you can see real scores in your log for a week, you have the data to pick a threshold that is not guesswork.

Implementation Patterns

Rule-based validation (the default path)

The lowest-friction integration is a form request or inline rule, so the token never reaches your controller action:

$request->validate([
    'g-recaptcha-response' => ['required', 'recaptcha_enterprise'],
]);

Prefer this for anything where you only need a pass/fail gate. It keeps the assessment inside Laravel's validation lifecycle, which means existing ValidationException handling and error bag rendering just work.

Manual assessment (when you need the score)

For login, registration, and anything with friction you want to tune, do the assessment yourself and branch:

$assessment = RecaptchaEnterprise::assess(
    token: $request->input('g-recaptcha-response'),
    action: 'login',
);

Then make an explicit decision rather than relying on a boolean:

  • score >= 0.7 → proceed
  • 0.3 <= score < 0.7 → proceed but require email/2FA challenge
  • score < 0.3 → block, log riskAnalysis.reasons and tokenProperties.invalidReason

Persist the score against the user or the attempt. A threshold with no historical data behind it is a coin flip, and you will want it when someone complains their legitimate traffic is being blocked.

Middleware for route groups

Apply it to a group rather than per-route so the policy is visible in routes/web.php:

Route::middleware('recaptcha.enterprise:contact,0.5')->group(function () {
    Route::post('/contact', ContactController::class);
});

The action name in the middleware string must match the action the frontend passes to grecaptcha.enterprise.execute(). Mismatched actions are the single most common source of "it always fails in staging."

Frontend

Enterprise does not use the v2 checkbox. It is the v3-style invisible flow:

<script src="https://www.google.com/recaptcha/enterprise.js?render={{ config('recaptcha.site_key') }}"></script>
<script>
grecaptcha.enterprise.ready(async () => {
  const token = await grecaptcha.enterprise.execute('{{ config('recaptcha.site_key') }}', { action: 'contact' });
  document.querySelector('#recaptcha-token').value = token;
});
</script>

If the package ships a Blade component or directive, use it — it usually handles the site key config lookup and the token field name that the rule expects. If it does not, keep the site key out of the template with config() so staging and production keys cannot drift.

Testing

Bind a fake before your test suite touches the rule. If the package wraps the Google SDK, swap the client in the container; if it uses REST under the hood, Http::fake() on recaptchaenterprise.googleapis.com is sufficient. Either way, add one test that asserts your threshold logic fires correctly at boundary scores — that is where the real bugs live, not in the happy path.

Gotchas and Tips

Tokens are single-use and short-lived (~2 minutes). Never call assess() twice for one token — the second call fails and you have paid for it. This bites people who put both middleware and a manual rule on the same route. Pick one.

Assessments are a billed API call. Google charges per assessment. A middleware plus a controller check is double billing on every request. If you need the score in your controller, use manual assessment only and skip the middleware.

Read the actual 1.0.0 diff. The release notes are a bare changelog link. Before adopting, run:

composer require codein-labs/recaptcha-enterprise-bundle:^1.0 --dry-run

and diff config/recaptcha.php against what you expect. A 1.0.0 bump from a 0.x line is exactly where config key renames hide, and config:cache will not warn you — it will just silently use stale keys and every assessment will fail with a credentials error you misdiagnose as a permissions problem.

Thresholds are action-specific. Google's docs recommend different thresholds per action because the score distribution differs by traffic type. Hardcoding 0.5 globally will over-block on some forms and under-block on others. Store thresholds in config keyed by action, not as a magic number in a rule string.

Score 0.0 usually means an invalid token, not a bot. Check tokenProperties.valid and tokenProperties.invalidReason before you act on the score. Common causes: expired token, token reused, action mismatch, or a site key / project ID pair from different projects.

Credentials and config:cache. Set GOOGLE_APPLICATION_CREDENTIALS to an absolute path (or use inline credentials from env) — relative paths break once the config is cached and the working directory differs between web and queue workers.

Fail-open vs fail-closed on network errors. Decide deliberately. For a contact form, fail-open with a log entry is usually right. For login, fail-closed. If the package exposes a config flag for this, set it per-use-case rather than globally; if it does not, wrap the call in a try/catch and make the decision explicit in your own code so it is reviewable.

Local development. Do not point dev at production credentials and burn quota generating noise. Either exempt the middleware when app()->environment('local'), or use Google's documented test site keys which always return fixed scores.

CSP and proxies. enterprise.js needs www.google.com and www.gstatic.com in script-src, and the assessment endpoint needs to be reachable from your server. Behind a corporate proxy or in a locked-down VPC, this fails with a timeout that looks like a token problem.

Log the reasons, not just the score. riskAnalysis.reasons is what tells you why traffic was flagged. Log it on every rejection for the first few weeks; it is the only way to tune the threshold defensibly.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels