captchaapi/laravel
Composer 安装命令:
composer require captchaapi/laravel
包简介
Official Laravel SDK for captchaapi.eu — EU-hosted, GDPR-compliant proof-of-work CAPTCHA with native Livewire support.
README 文档
README
Official Laravel SDK for captchaapi.eu — EU-hosted, GDPR-compliant proof-of-work CAPTCHA. Drop-in Blade component, validation rule, and Livewire trait. No cookies, no tracking, no Google.
Why captchaapi.eu
- EU-hosted (Hetzner Nuremberg) — GDPR-compliant by default, no data ever leaves the EU.
- Proof-of-work — invisible to legitimate visitors, no friction puzzles to solve.
- Server-side verification — your backend confirms each response with captchaapi.eu over a single call, secured by your secret key. The same model every major CAPTCHA uses, so the secret never reaches the browser.
- Livewire-native — first-class trait + Blade wrapper instead of just plain HTML form support.
Requirements
- PHP 8.2+
- Laravel 12 or 13
- (Optional) Livewire 4 for the trait + livewire-form component
Installation
composer require captchaapi/laravel
Publish the config file:
php artisan vendor:publish --tag=captchaapi-config
Set the credentials in .env:
CAPTCHAAPI_SITE_KEY=pk_live_... CAPTCHAAPI_SECRET_KEY=sk_live_...
The site key is public and goes in the browser; the secret stays on your server. Get both from the project dashboard.
Usage
Plain HTML form
In your layout's <head> (renders the widget script and pushes config to
window.CAPTCHA_*):
<x-captchaapi::widget />
In your form, add data-captcha:
<form action="/contact" method="POST" data-captcha> @csrf <input type="email" name="email" required> <button type="submit">Send</button> </form>
In your validation:
use Captchaapi\Laravel\Rules\ValidCaptcha; $request->validate([ 'email' => ['required', 'email'], 'captchaapi_response' => ['required', 'string', new ValidCaptcha], ]);
Or via the string alias:
$request->validate([ 'email' => ['required', 'email'], 'captchaapi_response' => ['required', 'captcha'], ]);
Livewire
In your layout (same as above):
<x-captchaapi::widget />
In your Livewire component:
use Captchaapi\Laravel\Concerns\WithCaptcha; use Livewire\Component; class RegisterForm extends Component { use WithCaptcha; public string $email = ''; public function register(): void { $this->validateWithCaptcha([ 'email' => 'required|email', ]); // proceed — captchaapi_response has been verified } }
validateWithCaptcha() is sugar for $this->validate(array_merge($rules, $this->rulesForCaptcha())).
If you need to compose validateOnly() flows yourself, rulesForCaptcha() is still public.
In the component view, use the Livewire-aware form wrapper:
<div> <x-captchaapi::livewire-form action="register" class="space-y-4"> <input wire:model="email" type="email" required> <button type="submit">Register</button> </x-captchaapi::livewire-form> </div>
The wrapper sets data-captcha-mode="event", includes the hidden
captchaapi_response input, and dispatches to your Livewire register
method once the response arrives.
Showing the validation error
When ValidCaptcha rejects a response (invalid, expired, or already
used), Laravel attaches the message to the captchaapi_response field.
Render it with the included helper:
<x-captchaapi::error />
Defaults to :for="captchaapi_response" and renders a <p role="alert">.
Override the field name, the wrapping tag, and pass through any
attributes:
<x-captchaapi::error for="captchaapi_response" as="span" class="text-red-600" />
The component is a thin wrapper around Laravel's @error directive — if
you prefer to keep markup in your own templates, write it yourself:
@error('captchaapi_response') <p role="alert">{{ $message }}</p> @enderror
Both approaches work for plain HTML forms (after a redirect-with-errors)
and for Livewire (after validateWithCaptcha()).
Status element styling
Place a <div data-captcha-status></div> anywhere inside (or near)
your form to show the widget's current state to your users. Six
states exist (waiting, idle, solving, ready, error,
rate_limited), each rendered as an icon + a localised message. The
widget sets data-captcha-state="…" on the element so you can target
each state with CSS.
The status element is opt-in. A form without a
[data-captcha-status] child runs silently — submission still works,
only the visible signal is absent. There is no auto-injection.
Default colors
The widget injects a low-specificity stylesheet on first use that applies sensible default colors per state:
| State | Default color |
|---|---|
waiting |
#6b7280 grey |
idle |
#6b7280 grey |
solving |
#6b7280 grey |
ready |
#059669 emerald |
error |
#dc2626 red |
rate_limited |
#d97706 amber |
You don't have to write any CSS to get those colors. They appear
automatically on every [data-captcha-status] element the widget
finds.
Override one state
Write a higher-specificity rule (two attribute selectors instead of
one). The customer rule wins regardless of cascade order — no
!important needed:
[data-captcha-status][data-captcha-state="ready"] { color: #047857; } [data-captcha-status][data-captcha-state="error"] { color: #b91c1c; }
Take over completely (Tailwind / design system)
Add data-captcha-no-color to suppress the widget's default
stylesheet entirely. Your own classes / CSS apply cleanly:
<div data-captcha-status data-captcha-no-color class="text-cyan-700 dark:text-cyan-300"></div>
Note that waiting and ready share the shield icon and would be
visually identical without color. If you opt out of the widget
defaults, supply per-state colors in your own CSS so the two stay
distinguishable.
Disabling the package
Flip CAPTCHAAPI_ENABLED=false in .env to disable both the validation
rule and the widget without removing any wiring. The ValidCaptcha rule
passes silently and <x-captchaapi::widget /> renders nothing, so you
can keep the trait, the rule, and the Blade markup in place across
local, CI, and staging environments where no live site key is set.
Defaults to true, so existing installs keep working unchanged. This is
a permanent kill-switch, not a per-test bypass — for that, use
FakeCaptchaapi::enable() (see Testing).
Configuration reference
| Config key | ENV variable | Default | Purpose |
|---|---|---|---|
enabled |
CAPTCHAAPI_ENABLED |
true |
Master kill-switch. When false, the rule passes silently and the widget renders nothing. |
site_key |
CAPTCHAAPI_SITE_KEY |
null |
Public site key from the dashboard. Required for widget rendering. |
secret_key |
CAPTCHAAPI_SECRET_KEY |
null |
Project secret, sent as a Bearer token on the verify call. Server-side only. |
base_url |
CAPTCHAAPI_BASE_URL |
null |
Override the API origin for the widget and the verify call. Defaults to https://captchaapi.eu. |
timeout |
CAPTCHAAPI_VERIFY_TIMEOUT |
5 |
Seconds to wait for the verify call before treating the server as unreachable. |
fail_open |
CAPTCHAAPI_FAIL_OPEN |
true |
When the server is unreachable or returns a 5xx: true lets the submission through, false rejects it with a try-again message. |
locale |
CAPTCHAAPI_LOCALE |
null |
Force widget language (en, de, cs, …). Falls back to <html lang> then en. |
preload |
CAPTCHAAPI_PRELOAD |
lazy |
lazy waits for first form interaction; eager fires the challenge on page load. |
debug |
CAPTCHAAPI_DEBUG |
false |
Log timing breakdown in the browser console. |
mode |
CAPTCHAAPI_MODE |
null |
submit (native form POST) or event (CustomEvent for Livewire/SPA). |
Fail policy
The verify call can fail to reach a verdict — the server is unreachable or
returns a 5xx. fail_open decides what happens, and it defaults to true:
the submission goes through. A CAPTCHA guards a public form, so your own
outage blocking every submission is worse than the rare bot slipping past
during it, and an attacker can't reach this path anyway — verification is
server-to-server, off the browser.
Set it to false for sensitive actions (login, payment) where a missed bot costs more than a blocked visitor:
CAPTCHAAPI_FAIL_OPEN=false
The visitor is then asked to try again, never told they failed the CAPTCHA. There is no automatic retry on either setting: the response is single-use, and a second verify call would spend a token the visitor already solved.
Secret key rotation
Rotate from the dashboard — the package needs no list of keys. While a rotation is pending, the server accepts both the old and the new secret on every challenge issued, so you deploy the new key without a hard cutover:
- In the dashboard, click Rotate secret key — issues a new key while the current one keeps working.
- Update
CAPTCHAAPI_SECRET_KEYin your.envand deploy. From here both keys verify, so the timing of your deploy doesn't matter. - In the dashboard, activate the new key, then retire the old one.
The only thing tied to the old key is a challenge a visitor was already solving when you clicked Rotate — it lives at most the token lifetime (~2 minutes), so any brief overlap clears itself.
For a suspected compromise, use Revoke immediately to drop the old key in one step. This skips the overlap, so in-flight solutions fail until the new secret is deployed.
Testing
In feature tests, enable fake mode so ValidCaptcha accepts any input
without a real response or a call to the server:
use Captchaapi\Laravel\Testing\FakeCaptchaapi; beforeEach(function () { FakeCaptchaapi::enable(); }); afterEach(function () { FakeCaptchaapi::disable(); });
The fake state is stored on the Captchaapi singleton — it does not
persist across requests in production code.
Catching Fortify's double validation
Fortify runs the login validator twice in one request, so a single-use response must survive both passes. To prove your integration handles this instead of silently bypassing it, opt the fake into the server's single-use contract:
FakeCaptchaapi::enable(); FakeCaptchaapi::enforceSingleUse();
The value now verifies once per request — the per-request memo covers the double call — while a replay in a later request is rejected. A missing memoization then fails the test instead of passing silently.
Laravel Octane
The package is safe under Octane (Swoole, RoadRunner, FrankenPHP), where the application and its singletons stay resident across requests:
- Verification is stateless. Each check makes its own call to
/verifyand reads the secret from config; nothing is cached on the manager between requests. - The per-request memo lives on the request, not the singleton. Octane hands each request a fresh request instance, so the memo never carries over to the next visitor — every request verifies independently.
- Fake state is test-only.
fake()andenforceSingleUse()throw outside thetestingenvironment, so the bypass and the consumed-token set can never be left set on a production worker.
Security
If you discover a security vulnerability, please do not open a public GitHub issue. Use GitHub's private vulnerability reporting so we can coordinate a fix before details become public.
Contributing
Bug reports and feature requests welcome at github.com/captchaapi/laravel/issues.
For development:
composer install composer test # Pest, parallel composer lint # Pint --test composer stan # PHPStan level 8 composer rector # Rector --dry-run
License
MIT — see LICENSE.
captchaapi/laravel 适用场景与选型建议
captchaapi/laravel 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 484 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 05 月 03 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「captcha」 「laravel」 「proof-of-work」 「pow」 「eu」 「spam-protection」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 captchaapi/laravel 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 captchaapi/laravel 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 captchaapi/laravel 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Diese Contao 4 Erweiterung stellt Google reCAPTCHA V2 in Form eines neuen Formularfeldes im Formulargenerator bereit. This extension provides Google reCAPTCHA V2 in the form of a new form field in the form generator of Contao Open Source CMS.
Two Captcha
Laravel 5 Securimage helper
Hashcash implementation in PHP.
Zend Captcha bundle
PHP port of the Cap proof-of-work captcha server. Wire-compatible with @cap.js/widget.
统计信息
- 总下载量: 484
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 47
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-05-03