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

gog/recaptcha-firewall

gog/recaptcha-firewall is a Laravel middleware package that verifies Google reCAPTCHA tokens before requests reach your routes, helping block bots and spam submissions while letting legitimate users through with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you install anything, read the source. This package has negligible public adoption (no stars, no dependents), so there is no community safety net, no third-party security review, and no Stack Overflow corpus to fall back on. Treat it as code you are adopting, not a dependency you are consuming. Budget 30–45 minutes to read src/ and composer.json before it touches a production app.

Prerequisites and first steps:

composer require gog/recaptcha-firewall
php artisan vendor:publish --provider="Gog\RecaptchaFirewall\RecaptchaFirewallServiceProvider"

Then open, in this order:

  1. composer.json — check the illuminate/* constraints against your Laravel version, and whether extra.laravel.providers is declared (auto-discovery) or you must register the provider manually in config/app.php.
  2. The published config file — confirm the actual filename after publishing (likely config/recaptcha-firewall.php). You need to know the env key names for site key/secret, the score threshold, and any route-exemption array before you write a single line of integration code.
  3. The middleware class — this is the whole package. Look for: how it reads the token (request input name vs. header), whether it calls Google synchronously, what it does on HTTP failure/timeout, and whether it returns 403, redirects, or aborts.
  4. README — for the intended usage shape; cross-check every claim against the source, since a zero-star README is often aspirational.

Your first use case should be the narrowest one that proves the wiring: protect a single public form (contact or login) behind the middleware on one route, with logging enabled, and watch the output for a day before widening it.

// routes/web.php — start here, not with global middleware
Route::post('/contact', [ContactController::class, 'store'])
    ->middleware('recaptcha.firewall'); // confirm alias from the provider

Implementation Patterns

Scope the middleware deliberately. The three tiers, in increasing risk:

  • Per-route — best default. You know exactly which forms are gated and you cannot accidentally lock out webhooks.
  • Route group — good for auth flows (/login, /register, /password/reset), which are the highest-value targets for credential stuffing and are the reason this package exists.
  • Global — almost always wrong. It will catch health checks, Stripe/GitHub webhooks, OAuth callbacks, and API clients that never had a token. If the package only ships a global option, wrap it in a route group instead.

Exempt the machine traffic explicitly. Whatever the exemption mechanism is (config array of route names, except URIs, or a shouldSkip() check in the middleware), populate it on day one:

'exempt' => [
    'stripe/webhook',
    'health',
    'api/*',        // token-authenticated, not browser traffic
],

Client-side integration. reCAPTCHA v3 issues short-lived, single-use tokens, so the token must be minted at submit time and attached to the form payload. Illustrative shape — verify the expected input name from the middleware source, as it is often not the default g-recaptcha-response:

<form method="POST" action="/contact" id="contact-form">
    @csrf
    <input type="hidden" name="g-recaptcha-response" id="recaptcha-token">
    ...
</form>
<script src="https://www.google.com/recaptcha/api.js?render={{ config('recaptcha-firewall.site_key') }}"></script>
<script>
document.getElementById('contact-form').addEventListener('submit', function (e) {
    if (this.dataset.tokenized) return;
    e.preventDefault();
    grecaptcha.ready(() => {
        grecaptcha.execute('{{ config('recaptcha-firewall.site_key') }}', { action: 'contact' })
            .then(token => {
                document.getElementById('recaptcha-token').value = token;
                this.dataset.tokenized = '1';
                this.submit();
            });
    });
});
</script>

Tune the threshold from data, not from defaults. Enable whatever logging the package offers (or add your own around it) and collect a week of score distributions before you pick a cutoff. A threshold of 0.5 is a common starting point for v3, but it is wrong for many audiences — privacy-conscious users, corporate VPNs, and shared IPs score low legitimately. If the package exposes the score on the request object, log it on rejection so you can see why people were blocked.

Pair it with throttle, don't replace it. reCAPTCHA is a signal, not a rate limiter. ->middleware(['throttle:5,1', 'recaptcha.firewall']) gives you defense in depth and a cheaper first line.

Testing. You need a way to disable verification in CI and in phpunit.xml. Find it before you write tests — a config flag (RECAPTCHA_ENABLED=false) or a bindable verifier interface you can swap with a fake. If neither exists, that absence is a strong signal about the package's maturity.

Gotchas and Tips

  • Treat it as unaudited code. Pin the exact version in composer.json (no ^ caret), and consider vendoring or forking it into your repo. Zero dependents means a silent upstream change or abandonment will not be caught by anyone but you.
  • Token lifecycle is the #1 source of "it randomly fails." Tokens expire in ~2 minutes and are single-use. Cached HTML with a pre-rendered token, multi-step wizards holding a token across steps, and users who take a long time to fill a form will all fail. Always mint at submit, never at page render.
  • A second submit from the same page needs a fresh token. If the package doesn't clear the token after use, your own retry logic can double-spend it. Reset the hidden field on any validation-error redisplay.
  • Fail-open vs. fail-closed. Find what the middleware does when Google's endpoint times out or returns 5xx. Defaulting to block means a Google outage takes your login page down; defaulting to allow means an attacker can sometimes induce that path. For a login form, most teams want fail-open plus an alert; for a payment flow, fail-closed. Whichever it does, make sure it is explicit — and confirm the HTTP call has a timeout, because no timeout plus fail-closed equals a hung request.
  • Proxy/CDN IP handling. v3 verdicts factor in the client IP. If you are behind Cloudflare/ALB and Laravel's TrustProxies isn't configured, you are sending Google your load balancer's IP and every request looks suspicious. Fix TrustProxies before blaming the package.
  • Action mismatches fail silently-ish. If you call grecaptcha.execute(..., {action: 'contact'}) but the server verifies against 'login', verification fails even with a 0.9 score. Confirm whether the package validates action at all — many naive implementations don't, which is itself a hole worth knowing about.
  • Local dev and CI. reCAPTCHA does not work on localhost with real keys in a useful way. Have an env-driven bypass, and make sure it is false in production by a config check rather than by convention.
  • Debugging. Log the score, the action, the hostname, and the returned error codes — never the token or the secret. If the package's exception message is just "reCAPTCHA failed," wrap the middleware in your own try/catch or add a listener so you get the underlying Google error codes.
  • Middleware ordering matters. If the package needs the session or the authenticated user, it must run after StartSession/Authenticate. If it short-circuits before ValidateCsrfToken, you have bypassed CSRF protection on that route — verify the ordering in Http/Kernel.php or bootstrap/app.php.
  • Extension points to look for: a RecaptchaVerifier-style interface bound in the container (swap it for a fake or a multi-provider fallback), a publishable config (so you can version-control settings), and a Blade directive. If none exist, fork — a ~200-line middleware is cheaper to own than to work around.
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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle