jakreiter/recaptcha2-bundle-proxyable
A Symfony/PHP bundle that integrates Google reCAPTCHA v2 and adds proxy support for verification requests. Configure keys, render the widget, and validate form submissions through a proxy when required, making spam protection easier to deploy in restricted network environments.
First, level-set: this is a Symfony bundle, not a Laravel package. There is no RecaptchaServiceProvider, no facade, no auto-discovery. jakreiter/recaptcha2-bundle-proxyable is a fork of beelab/recaptcha2-bundle whose only reason to exist is a configurable outbound HTTP proxy for the server-to-Google verification call. Everything else — form type, Twig widget, validator, service wiring — is Symfony-shaped and largely useless in a Laravel app.
Day-one checklist
BeelabRecaptcha2Bundle), not the fork's. The fork inherits its config keys, form type, and validator almost verbatim; the only delta you care about is the proxy option. Check src/DependencyInjection/Configuration.php in the fork to see the exact key name (proxy, http_proxy, or a DSN) and whether it is optional.google/recaptcha plus a proxy-aware RequestMethod. The bundle is ~3 service definitions and a constraint validator; porting that is 30–60 lines.Minimal Laravel wiring
composer require jakreiter/recaptcha2-bundle-proxyable google/recaptcha
// config/services.php
'recaptcha' => [
'site_key' => env('RECAPTCHA_SITE_KEY'),
'secret_key' => env('RECAPTCHA_SECRET_KEY'),
'proxy' => env('RECAPTCHA_PROXY'), // http://user:[email protected]:3128
],
// app/Services/RecaptchaVerifier.php
namespace App\Services;
use ReCaptcha\ReCaptcha;
use ReCaptcha\RequestMethod\CurlPost;
class RecaptchaVerifier
{
public function verify(?string $token, ?string $ip = null): bool
{
if (blank($token)) {
return false;
}
// NOTE: verify this constructor signature against the installed
// google/recaptcha version; proxy support has moved between
// CurlPost::__construct() and ReCaptcha::setHttpClient() over time.
$method = new CurlPost(config('services.recaptcha.proxy'));
$response = (new ReCaptcha(config('services.recaptcha.secret_key'), $method))
->setExpectedHostname(request()->getHost())
->verify($token, $ip ?? request()->ip());
if (! $response->isSuccess()) {
logger()->warning('recaptcha rejected', ['codes' => $response->getErrorCodes()]);
}
return $response->isSuccess();
}
}
First use case: a public contact form or registration form with a visible v2 checkbox. Render the widget in Blade, post g-recaptcha-response, verify server-side before persisting. The proxy matters only in the server→Google direction; the widget script is still fetched by the end user's browser from www.google.com.
1. Validation rule wrapper (the Laravel analogue of the bundle's Recaptcha2 constraint).
class RecaptchaRule implements \Illuminate\Contracts\Validation\ValidationRule
{
public function __construct(private RecaptchaVerifier $verifier) {}
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if (! $this->verifier->verify($value)) {
$fail('Please confirm you are not a robot.');
}
}
}
Then 'g-recaptcha-response' => ['required', new RecaptchaRule(app(RecaptchaVerifier::class))] in a FormRequest. This gives you the same ergonomics the Symfony bundle gets from its Recaptcha2Validator — one attribute, one constraint, no controller clutter.
2. Middleware for repeat offenders. For login and password-reset endpoints, put the check in a small middleware so you don't have to remember it in every controller. Keep it opt-in via route groups; captchas on every request wreck UX for authenticated users.
3. Invisible v2 with callback. The bundle's Twig theme handles this for Symfony; in Blade you write it yourself:
<div class="g-recaptcha" data-sitekey="{{ config('services.recaptcha.site_key') }}"
data-callback="onRecaptcha"></div>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
4. Config-driven disabling. The upstream bundle exposes enabled: false for test environments and the fork inherits it. In Laravel, mirror this with a config flag and a RecaptchaVerifier::verify() that short-circuits to true when app()->environment('testing', 'local') or config('services.recaptcha.enabled') === false. This is the single most valuable thing you take from the bundle — otherwise your feature tests all need a fake token.
5. Proxy via container config, not code. Whatever the fork's key is called, surface it as an env var and let ops set it per-environment. Do not hardcode a proxy DSN; corporate egress proxies rotate and you will be redeploying.
6. Front-end integration tips. Load api.js with async defer, put the widget below the fold for long forms, and always give the user a visible error path — Google returning timeout-or-duplicate should look like "please try again", not "you are a bot".
symfony/http-kernel, symfony/form, symfony/validator, and symfony/config into your vendor/. That's hundreds of classes and a second DI container you will never use. If your only requirement is "call Google through a proxy", install google/recaptcha directly and save yourself the conflicts.beelab/recaptcha2-bundle before adopting, and check whether its composer.json declares replace on the upstream package — if it does, you can't have both installed, and any other package depending on upstream will silently get the fork.last release: 2025-02-27 is recent, but "in progress" in the description is doing real work here. Treat the proxy path as unproven: write an integration test that hits a local proxy and asserts the request went through it (or at least that a bad proxy produces a network failure, not a "captcha invalid").ReCaptcha\Response::isSuccess() is false for both a bad token and a connection timeout through the proxy. getErrorCodes() will be empty on transport failure. Log the difference and show a retry message, or you will be debugging "the captcha is broken" tickets that are actually firewall tickets.www.google.com to render the widget. In an air-gapped or China-style egress-restricted network, a server-side proxy solves exactly half the problem. Consider reCAPTCHA Enterprise's separate domains or an alternative if the browser side is also blocked.remoteip behind load balancers. request()->ip() only works if TrustProxies is configured for your LB. Passing the LB's IP to Google weakens the check and pollutes their risk signals.http://user:pass@host:3128 will show up in config:cache output, exception traces, and any debug toolbar that dumps config. Prefer separate env vars and inject them where possible.phpunit.xml, set RECAPTCHA_ENABLED=false and have your RecaptchaRule honour it. For a true end-to-end test, bind a fake RecaptchaVerifier in the container rather than mocking Guzzle internals.google/recaptcha plus your own ~40 lines avoids the question entirely.Beelab\Recaptcha2Bundle\Recaptcha\RecaptchaService rather than subclassing; override the form type with a compiler pass only if you need custom widget attributes. For Laravel, the equivalent extension point is a container binding for RecaptchaVerifier, which is exactly what the code above gives you.How can I help you explore Laravel packages today?