zhylon / captcha-laravel
Laravel integration for server-side verification of Zhylon Captcha tokens.
Requires
- php: ^8.2
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- zhylon/captcha-php: ^1.1
Requires (Dev)
- laravel/pint: ^1.30
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.0
README
Laravel integration for server-side verification of Zhylon Captcha tokens. Built on top of zhylon/captcha-php.
Requirements
- PHP 8.2+
- Laravel 12.x or 13.x
Installation
composer require zhylon/captcha-laravel
Publish the config file:
php artisan vendor:publish --tag=config
Set your secret key in .env:
ZHYLON_CAPTCHA_SITE_KEY=your-site-key
ZHYLON_CAPTCHA_SECRET_KEY=your-secret-key
Usage
Via the Captcha facade:
use Zhylon\CaptchaLaravel\Facades\Captcha; $result = Captcha::verify($request->input('captcha_token')); if (! $result->success) { // $result->reason is one of: invalid_token, config_error, network_error // $result->message contains a human-readable explanation abort(403, $result->message); }
Or inject Zhylon\CaptchaPhp\CaptchaClient wherever you need it — it's bound as a singleton in the container.
Self-hosted challenge/verify proxy
If your widget's JS should talk to your own domain instead of captcha.zhylon.net
directly, the package can forward the captcha/challenge and captcha/verify
calls for you. It reuses the host already configured via verify_url, so no extra
config is needed, and upstream failures never surface as an exception — you always
get back a status code and JSON body.
Fastest option — built-in routes. Enable them and the package
registers GET {prefix}/challenge and POST {prefix}/verify for you, backed
by Zhylon\CaptchaLaravel\Http\Controllers\CaptchaProxyController:
ZHYLON_CAPTCHA_PROXY_ENABLED=true
By default this exposes GET /captcha/challenge and POST /captcha/verify.
Adjust the prefix or middleware via config (see below) — e.g. to put them behind
throttle or api.
Manual option — the CaptchaProxy facade. If you want your own route/controller
(custom path, extra validation, etc.), call the facade directly:
use Zhylon\CaptchaLaravel\Facades\CaptchaProxy; Route::get('pow/challenge', function () { $response = CaptchaProxy::challenge(['sitekey' => request()->get('sitekey')]); return response()->json($response->body, $response->status); }); Route::post('pow/verify', function () { $response = CaptchaProxy::verify(request()->only(['nonce', 'solve_ms', 'token'])); return response()->json($response->body, $response->status); });
Or inject Zhylon\CaptchaPhp\CaptchaProxy — it's also bound as a singleton, derived from the same CaptchaClient.
Note: CaptchaProxy::verify() forwards the proof-of-work captcha/verify call (returns a verification_token). It is unrelated to Captcha::verify() / CaptchaClient::verify(), which is the server-side siteverify check performed with your secret key.
Config
| Key | Env variable | Default | Description |
|---|---|---|---|
site_key |
ZHYLON_CAPTCHA_SITE_KEY |
— | Your Zhylon Captcha site key |
secret_key |
ZHYLON_CAPTCHA_SECRET_KEY |
— | Your Zhylon Captcha secret key |
verify_url |
ZHYLON_CAPTCHA_VERIFY_URL |
https://captcha.zhylon.net/captcha/siteverify |
Verification endpoint |
timeout |
ZHYLON_CAPTCHA_TIMEOUT |
5 |
Request timeout in seconds |
proxy.enabled |
ZHYLON_CAPTCHA_PROXY_ENABLED |
false |
Registers the built-in challenge/verify routes |
proxy.prefix |
ZHYLON_CAPTCHA_PROXY_PREFIX |
captcha |
Route prefix for the built-in proxy routes |
proxy.middleware |
— | ['api'] |
Middleware applied to the built-in proxy routes |
proxy.timeout |
ZHYLON_CAPTCHA_PROXY_TIMEOUT |
5 |
Timeout for the forwarded challenge/verify calls |
CaptchaResult
| Property | Type | Description |
|---|---|---|
success |
bool |
Whether verification succeeded |
reason |
?string |
invalid_token, config_error, or network_error on failure |
message |
?string |
Human-readable explanation |
Use $result->isTechnicalFailure() to distinguish infrastructure problems (config_error, network_error) from a genuinely invalid token.
Testing
composer test
License
The MIT License (MIT). See LICENSE for more information.