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 Laravel Package

google/recaptcha

google/recaptcha is the official PHP client for server-side verification of reCAPTCHA v2 and v3 responses, helping protect forms and APIs from spam and abuse. It supports Composer installation and requires PHP 8+.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

  • Pros:

    • Persistent-Worker Safety (Critical for Laravel): Immutable with*() builders (withExpectedHostname(), withExpectedApkPackageName(), withExpectedAction(), withScoreThreshold(), withChallengeTimeout()) return clones instead of mutating state (#637). This makes ReCaptcha safe to bind as a singleton in Laravel's container under Octane, FrankenPHP, RoadRunner, and Swoole, where the previous mutating setters risked cross-request configuration bleed.
    • Timeout Configuration Resolved: Optional int $timeout = 60 on CurlPost, Post, and SocketPost, plus CURLOPT_CONNECTTIMEOUT (#636). The prior assessment flagged timeouts as a fixed latency risk; they are now tunable per environment via config/services.php.
    • Serialization Concern Resolved: Response implements \JsonSerializable (#636). The previous readonly/DTO serialization risk is largely eliminated—responses can be json_encode()d directly for caching, queue payloads, and API responses without custom shims.
    • Transport Consistency: Unified HTTP 200 validation across CurlPost, Post, and SocketPost with consistent ReCaptcha::E_BAD_RESPONSE (#637). Deterministic error mapping in middleware instead of transport-specific surprises.
    • Latency & Memory: CurlPost lazily initializes and reuses its CurlHandle for TLS session resumption and HTTP keep-alive (#637)—directly reduces p95 on high-volume verification paths. SocketPost now loops fwrite() for partial TLS writes and closes handles cleanly on failure.
    • Secret Hygiene: #[\SensitiveParameter] on $secret in ReCaptcha::__construct() and RequestParameters::__construct() (#636) prevents the secret surfacing in Laravel's exception handler, Ignition, Sentry, and log stack traces.
    • Restricted-Network Support: ReCaptcha::SITE_VERIFY_URL_ALTERNATIVE (www.recaptcha.net) (#636) enables deployments where www.google.com egress is blocked—previously a hard blocker for some regional/CI environments.
    • Two Silent-Bypass Classes Closed: setScoreThreshold(0.0) no longer fails open when score is null (#637), and setChallengeTimeout() now fails closed with E_CHALLENGE_TIMEOUT on empty/unparseable challenge_ts (#637). Both were fail-open auth weaknesses in request-path verification.
    • Type Robustness: error-codes filtered to string elements in Response::fromJson() (#636), preventing type errors in strict-typed consumers and sanitizers.
  • Cons:

    • Fail-Closed Behavior Change: challenge_ts handling now fails closed. Any integration that omits challenge_ts, synthesizes tokens in tests, or runs on hosts with clock skew may now receive E_CHALLENGE_TIMEOUT where it previously passed. This is a security-correct change but an observable behavioral one.
    • Legacy Setters Still Present: Mutating setters remain for backward compatibility. Teams can still call them on a container singleton and reintroduce state bleed under Octane—immutability is opt-in, not enforced.
    • Connection Lifetime Under Long-Lived Workers: Reused CurlHandle instances persist across requests in Octane workers. Stale keep-alive connections, DNS/IP changes, or proxy re-resolution can surface as intermittent failures requiring retry logic.
    • Default Timeout Still Generous: 60s default is inappropriate for a synchronous form-submission path; it must be explicitly lowered.
    • PHP >=8.4 Hard Floor: The 2.x line requires PHP 8.4, which constrains the Laravel matrix to 10+/11+/12+.

Integration Feasibility

  • Container Binding (now safe as a singleton):
    $this->app->singleton(\ReCaptcha\ReCaptcha::class, function ($app) {
        return (new \ReCaptcha\ReCaptcha(
            secret: config('services.recaptcha.secret'),
            transport: new \ReCaptcha\RequestMethod\CurlPost(
                timeout: (int) config('services.recaptcha.timeout', 5),
            ),
        ))
            ->withExpectedHostname(config('services.recaptcha.hostname'))
            ->withExpectedAction(config('services.recaptcha.action'))
            ->withScoreThreshold((float) config('services.recaptcha.score_threshold', 0.5))
            ->withChallengeTimeout((int) config('services.recaptcha.challenge_timeout', 120));
    });
    
    Note: with*() returns a clone—do not discard the return value, and do not call legacy setters on the resolved singleton.
  • Endpoint Configuration: expose services.recaptcha.verify_url defaulting to ReCaptcha::SITE_VERIFY_URL, overridable to SITE_VERIFY_URL_ALTERNATIVE for restricted-egress environments.
  • Middleware / Validation Rule: map error codes explicitly rather than treating any !isSuccess() as a generic bot:
    $response = $recaptcha->verify($request->input('g-recaptcha-response'), $request->ip());
    return match (true) {
        $response->isSuccess()                                                   => $next($request),
        in_array(ReCaptcha::E_CHALLENGE_TIMEOUT, $response->getErrorCodes(), true) => back()->withErrors(['recaptcha' => 'Verification expired, please retry']),
        in_array(ReCaptcha::E_BAD_RESPONSE, $response->getErrorCodes(), true)      => back()->withErrors(['recaptcha' => 'Verification unavailable, please retry']),
        default                                                                  => back()->withErrors(['recaptcha' => 'Verification failed']),
    };
    
  • Testing Impact: Existing Mockery doubles on the transport remain valid (transport constructors only gained an optional parameter). New test cases worth adding: empty challenge_ts, score omitted with threshold 0.0, non-200 transport responses, non-string error-codes, and a json_encode($response) contract test.
  • Upgrade Cost: composer update google/recaptcha; 2.1.0 is verified backward-compatible with 2.0.0 via roave/backward-compatibility-check. Effort concentrates on behavioral validation (fail-closed paths), not API rewrites.

Technical Risk

  • High:

    • None newly introduced. The two previously-high risks from the last assessment—strict-null DTO breakage and response serialization—are materially reduced (JsonSerializable added; 2.1.0 is BC with 2.0.0).
  • Medium:

    • Fail-Closed challenge_ts Semantics: Silent auth-bypass removal can produce new user-facing failures (expired v3 tokens, clock drift, load-balanced hosts without NTP). Must be validated against real traffic and paired with a distinct user-facing retry message.
    • Octane State Bleed via Legacy Setters: Any code calling setExpectedHostname() / setScoreThreshold() on the injected singleton reintroduces cross-request mutation. Static analysis / code review rule required.
    • Kept-Alive cURL Connections: Reused handles across worker lifetimes can pin stale TLS sessions through proxy/LB churn. Requires bounded retry with backoff and per-request timeout budget.
  • Low:

    • jsonSerialize() Output as Public Contract: Once response JSON is persisted or emitted, its shape becomes an API surface—treat changes to it as versioned.
    • error-codes String Filtering: Consumers assuming non-string entries will silently see fewer codes.
    • #[\SensitiveParameter] Scope: Prevents future leakage only; secrets already captured in historical logs/APM traces are unaffected and may warrant rotation.
    • Alternative Endpoint: Behaviorally identical API surface; low risk, but needs egress/DNS allowlisting verified in each environment.

Key Questions

  1. Fail-Closed Impact: Which flows omit, truncate, or fabricate challenge_ts (custom token wrappers, v3 invisible flows, E2E test harnesses)? Is NTP/clock sync guaranteed across all app hosts and load balancers?
  2. Runtime Topology: Are we on Octane/RoadRunner/FrankenPHP/Swoole today or planned? If yes, can we enforce "no mutating setters on injected instances" via CI lint or static analysis?
  3. Timeout Budget: What is the acceptable end-to-end verification budget for a login/checkout form, and what timeout/connect_timeout values should be standardized per environment? Should non-critical forms fail open to a challenge, or hard-fail?
  4. Endpoint Strategy: Do any target regions/CI runners need SITE_VERIFY_URL_ALTERNATIVE, and is it a static config or a runtime fallback?
  5. Secret Hygiene: Are reCAPTCHA secrets present in existing log/APM history, and what is the rotation procedure and blast radius?
  6. Error-Code Contracts: Does any downstream consumer (dashboards, alerting, SPA clients) branch on specific error-codes values or assume non-string entries?

Integration Approach

Stack Fit

  • Service Provider: Register ReCaptcha as a singleton using the immutable builder chain (see snippet above). Bind RequestMethod\CurlPost explicitly so the timeout is ours, not the 60s default; keep Post/SocketPost only as fallbacks for environments without cURL.
  • Configuration Surface (config/services.php → recaptcha):
    • secret, site_key, version
    • timeout (default 5), connect_timeout (default 2)
    • verify_url (default ReCaptcha::SITE_VERIFY_URL, override to SITE_VERIFY_URL_ALTERNATIVE)
    • hostname, apk_package_name, action, score_threshold (default 0.5), challenge_timeout (default 120)
  • Validation Rule / Middleware: Keep the core rule; branch on error codes for user messaging and metrics (distinct counters for E_CHALLENGE_TIMEOUT, E_SCORE_THRESHOLD_NOT_MET, E_BAD_RESPONSE).
  • Logging: Log getErrorCodes() and request context only—never the token or secret. #[\SensitiveParameter] protects stack traces; it does not protect hand-written logger() calls, so redact explicitly.
  • Frontend: No changes required for v2/v3 widgets. Ensure SPA error handling distinguishes "expired challenge" (retry) from "verification unavailable" (fallback/abort), and that any rendering of API-supplied messages stays escaped via e().
  • Octane: Ensure the singleton is stateless by construction—all per-request variation (hostname/action/score) must be applied via with*() on a fresh local variable, not on the injected instance.

Migration Path

  1. Phase 0 — Audit (0.5–1 dev-day)
    • Grep for setExpectedHostname, setExpectedAction, setScoreThreshold, setChallengeTimeout, SocketPost, Post, and any direct Response serialization.
    • Confirm PHP >=8.4 and Laravel >=10 in composer.json; identify any service still pinned to 1.x.
    • Confirm clock sync (NTP/chrony) across app hosts and that all form flows submit challenge_ts-bearing tokens (i.e., server-side verification of freshly issued tokens).
  2. Phase 1 — Pilot (1 dev-day)
    • composer update google/recaptcha on a non-critical module; wire CurlPost with a 2s connect / 5s total timeout.
    • Add tests for empty challenge_ts, score: null with threshold 0.0, non-200 responses, and json_encode($response).
    • Validate the alternative endpoint in one restricted-egress environment if applicable.
  3. Phase 2 — Rollout (1–2 dev-days)
    • Adopt with*() across all call sites; remove remaining mutating setters.
    • Map error codes to distinct user messaging and metrics; add bounded retry (single retry, exponential/jittered) for E_BAD_RESPONSE on non-user-blocking paths only.
    • Audit all dynamic rendering of reCAPTCHA-derived data (Blade, API payloads, admin dashboards) for escaping.
  4. Phase 3 — Optimization & Observation (ongoing, 1 week watch)
    • Tune timeouts from observed p95/p99; confirm connection reuse is effective (latency drop) without stale-connection errors.
    • Rotate secrets if historical leakage in logs is confirmed.

Compatibility

  • Laravel: 10.x / 11.x / 12.x with PHP 8.4+. Laravel 9.x must remain on the 1.x line (no readonly, no #[SensitiveParameter], no 8.4 requirement).
  • Package Semantics: 2.1.0 is 100% backward-compatible with 2.0.0 per roave/backward-compatibility-check; constructor additions are optional parameters, so existing transport instantiation compiles unchanged.
  • Behavioral (not API) Changes: fail-closed challenge_ts, strict score null handling, and error-codes string filtering alter outcomes for edge inputs. These are the real upgrade risks, not signatures.
  • Extensions: curl and openssl required for connection reuse and TLS hardening; environments falling back to SocketPost still benefit from unified status validation but not from keep-alive.
  • Frontend Frameworks: No required changes; SPA/API clients should be reviewed for assumptions about success/error-codes presence and types.

Sequencing

  1. Prerequisites: PHP 8.4 runtime on staging and production images; curl/openssl enabled; config/services.php extended with timeout/endpoint/threshold keys; NTP verified.
  2. Core Update: composer update google/recaptcha; replace singleton construction with the immutable builder chain; remove mutating setters.
  3. Verification: Run PHPStan/Psalm at max level (catches discarded with*() returns and null-safety regressions); run the new edge-case tests; replay recorded production traffic against staging to detect any newly-failing challenge_ts paths.
  4. Rollout: Deploy behind a feature flag if verification is on a revenue path; watch error-code distribution for 24–72h before removing the flag.
  5. Hardening: Enable the alternative endpoint only where required; add alerting on E_CHALLENGE_TIMEOUT and E_BAD_RESPONSE rate deltas; confirm no secret appears in logs post-deploy.

Operational Impact

Maintenance

  • Low. 2.1.0 is a minor release with a 2.x BC promise and automated BC verification upstream. Pin to ^2.1 and let Renovate/Dependabot surface patches; the maintenance burden shifts from code churn to config tuning (timeouts, thresholds) and periodic secret rotation.
  • One ongoing hygiene task: prevent reintroduction of mutating setters on shared instances. Enforce via a static-analysis rule or php-cs-fixer custom rule rather than documentation alone.

Support

  • Runbook additions: define operator-facing actions for E_CHALLENGE_TIMEOUT (clock skew / stale token → ask user to retry), E_SCORE_THRESHOLD_NOT_MET (expected bot signal — do not page), E_BAD_RESPONSE (transport/egress problem — check proxy, DNS, alternative endpoint), and E_SCORE_THRESHOLD_NOT_MET with score: null (API contract anomaly).
  • Support can now distinguish transport failures from user failures because HTTP status handling is unified—previously a 500 from an intermediary could look like a verification failure.
  • Secret exposure in tickets/screenshots is reduced by #[\SensitiveParameter], but support scripts that dump request payloads must still be reviewed.

Scaling

  • Latency: Connection reuse (TLS session resumption + keep-alive) reduces per-verification handshake cost—most valuable at high form-submission volume and on login/checkout hot paths. Expected benefit is largest with the singleton + CurlPost combination.
  • Worker Topology: Octane/worker runtimes now get correct isolation via immutability; size worker pools assuming one shared transport per worker, not per request.
  • Throughput vs. Blocking: Verification remains synchronous. At high volume, keep the per-request timeout budget small (2s connect / 5s total) so a degraded Google endpoint cannot exhaust PHP-FPM/Octane workers. Consider queueing verification only for non-blocking flows.
  • Regional Scale: SITE_VERIFY_URL_ALTERNATIVE removes a hard scaling wall for restricted-egress regions; make it a config toggle so a single image serves all regions.

Failure Modes

  • Verification Endpoint Degradation → bounded by configured timeouts; without explicit config the 60s default can pin workers. Mitigation: explicit small timeouts + single jittered retry + circuit-breaker-ish fail-to-challenge path.
  • Stale Keep-Alive Connections (proxy/LB churn, DNS change) → intermittent E_BAD_RESPONSE under Octane. Mitigation: retry once on a fresh transport instance; monitor error-code rate by worker age.
  • Fail-Closed challenge_ts → elevated E_CHALLENGE_TIMEOUT for clients with clock skew or slow token submission. Mitigation: generous but bounded withChallengeTimeout() (e.g., 120s), explicit "please retry" UX, monitor submission latency.
  • Singleton State Bleed → cross-user config contamination (wrong expected action/hostname) if legacy setters are used under Octane. Mitigation: builder-only pattern + static analysis; canary check asserting expected action in a health endpoint.
  • Secret in Telemetry → historical log/APM exposure predating the #[\SensitiveParameter] fix. Mitigation: secret rotation, log scrubbing, verify no logger() call passes the secret.
  • Response Shape Drift → jsonSerialize() output consumed by caches/queues/SPAs; changing it later is a breaking change. Mitigation: version or normalize before persisting.

Ramp-Up

  • Engineer effort: ~2–4 dev-days total (audit 0.5–1, pilot 1, rollout 1–2) for a typical Laravel app with 1–3 verification call sites; +1 day if Octane adoption requires removing legacy setters across the codebase.
  • Learning curve: Low. with*() is a familiar Laravel pattern (immutable config objects); the main conceptual shift is "never mutate the injected instance."
  • Reviewer checklist: (1) with*() return values used, (2) no legacy setters on singletons, (3) timeouts explicitly configured, (4) all error codes mapped distinctly, (5) secrets redacted in hand-written logging, (6) escaping verified on any reCAPTCHA-derived output.
  • Documentation to update: config/services.php comments, the team runbook (error-code table), and the onboarding guide's "verifying reCAPTCHA" snippet—these three artifacts account for most of the ongoing support load.
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.
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
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums