oliweb/laravel-cap
Composer 安装命令:
composer require oliweb/laravel-cap
包简介
Laravel wrapper for Cap (tiagozip/cap) — self-hosted CAPTCHA alternative
README 文档
README
A Laravel wrapper for Cap — the self-hosted, privacy-friendly CAPTCHA alternative based on Proof-of-Work.
Cap works without tracking, cookies, or third-party services. This package integrates server-side token verification into Laravel through a service, a facade, a middleware, a validation rule, and Blade directives.
Requirements
- PHP ^8.2
- Laravel 11, 12, or 13
- A running Cap instance (self-hosted via Docker)
Installation
composer require oliweb/laravel-cap
The service provider and facade are registered automatically via Laravel's package auto-discovery.
Publish the configuration file:
php artisan vendor:publish --tag=cap-config
Publish the JS, CSS, and WASM assets (required for @capScripts and @capStyles):
php artisan vendor:publish --tag=cap-assets
This publishes the following files to public/vendor/cap/:
| File | Description |
|---|---|
cap-widget.js |
Cap widget (custom element + programmatic API) |
cap-widget.css |
Default widget styles |
cap_wasm_bg.wasm |
WebAssembly binary for proof-of-work (served locally, no CDN required) |
cap_wasm.js |
JS loader for the WASM module |
Publish the translation files (optional — to override messages):
php artisan vendor:publish --tag=cap-lang
Configuration
Add the following variables to your .env file:
CAP_ENDPOINT=https://cap.example.com/your-site-key/ CAP_SECRET=your-secret-key CAP_TOKEN_FIELD=cap-token CAP_TIMEOUT=5 CAP_FAIL_OPEN=false
| Variable | Description | Default |
|---|---|---|
CAP_ENDPOINT |
Full URL of your Cap instance including the site key (trailing slash required) | — |
CAP_SECRET |
Secret key from your Cap dashboard | — |
CAP_TOKEN_FIELD |
Name of the hidden field injected by the Cap widget | cap-token |
CAP_TIMEOUT |
HTTP timeout in seconds for the /siteverify request |
5 |
CAP_FAIL_OPEN |
When true, let requests through on network/server errors (see below) |
false |
CAP_FRAME_ROUTE |
URL path for the iframe route (used by @capFrame) |
cap-frame |
Fail-open mode
By default, any communication error with the Cap instance (network failure, timeout, HTTP 5xx) blocks the request, just like an invalid token would.
Setting CAP_FAIL_OPEN=true inverts this: communication errors silently pass, so a Cap outage does not take your forms down with it.
An explicitly invalid token (success: false) is always rejected regardless of this setting. Fail-open only covers infrastructure failures, not verification failures.
Translations
Server-side messages (validation rule, middleware) are translatable. English and French are included out of the box.
To override or add a language, publish the translation files and edit lang/vendor/cap/{locale}/messages.php:
php artisan vendor:publish --tag=cap-lang
// lang/vendor/cap/fr/messages.php return [ 'validation_failed' => 'La vérification :attribute a échoué. Veuillez réessayer.', 'middleware_failed' => 'La vérification Cap a échoué.', ];
Laravel selects the right file automatically based on App::getLocale().
Widget styling
Edit public/vendor/cap/cap-widget.css to override the CSS custom properties exposed by the widget:
cap-widget { --cap-color-primary: #6366f1; --cap-color-success: #22c55e; --cap-border-radius: 0.5rem; --cap-font-family: inherit; /* ... */ }
Usage
Blade directives
| Directive | Description |
|---|---|
@cap |
Renders <cap-widget> with the configured endpoint |
@capScripts |
Injects window.CAP_CUSTOM_WASM_URL + <script type="module"> for the widget |
@capStyles |
<link> loading the theme from public/vendor/cap/cap-widget.css |
@capConfig |
<script> exposing window.CAP_API_ENDPOINT and window.CAP_TOKEN_FIELD |
@capFrame |
Renders the Cap widget in an isolated iframe with a permissive CSP — the parent page keeps a strict CSP without 'unsafe-eval' |
Standard widget mode
Include the Cap widget in any Blade form:
@capStyles @capScripts <form method="POST" action="/contact"> @csrf @cap <button type="submit">Submit</button> </form>
The widget automatically injects a hidden cap-token field (or the value of CAP_TOKEN_FIELD) into its parent form upon successful verification.
@capScripts always injects window.CAP_CUSTOM_WASM_URL pointing to the locally published WASM, so no external CDN is contacted at runtime.
Programmatic mode
Use @capConfig to expose the endpoint to JavaScript, then instantiate Cap directly without rendering a visible widget:
@capConfig @capScripts <form method="POST" action="/contact"> @csrf <input type="hidden" name="cap-token" id="cap-token"> <button type="submit" id="submit-btn">Submit</button> </form> <script type="module"> document.getElementById('submit-btn').addEventListener('click', async (e) => { e.preventDefault(); const cap = new Cap({ apiEndpoint: window.CAP_API_ENDPOINT }); const { token } = await cap.solve(); document.getElementById('cap-token').value = token; e.target.closest('form').submit(); }); </script>
Cap creates a hidden cap-widget element in the background and exposes a solve() method that returns { token }. No visible widget is rendered.
window.CAP_API_ENDPOINT and window.CAP_TOKEN_FIELD are set by @capConfig from your PHP configuration, so you never need to hard-code the endpoint in JavaScript.
Iframe mode (strict CSP — no 'unsafe-eval')
When Cap's instrumentation is enabled, the widget requires 'unsafe-eval' in script-src. If your page enforces a strict CSP, use @capFrame instead: it serves the widget in a dedicated iframe (/cap-frame) with its own permissive CSP, keeping the parent page clean.
{{-- In your layout: no @capScripts or @capStyles needed --}} <form @submit.prevent="submitWithCap"> @csrf @capFrame(Vite::cspNonce()) <button type="submit">Submit</button> </form>
This renders:
<input type="hidden" name="cap-token" id="cap-frame-token"> <iframe src="/cap-frame" id="cap-frame" style="border:none;overflow:hidden;width:300px;height:58px;" title="Cap CAPTCHA" loading="lazy"></iframe> <script nonce="…"> (function(){ window.addEventListener('message', function(e) { if (e.origin !== window.location.origin) return; if (!e.data || e.data.type !== 'cap:token') return; document.getElementById('cap-frame-token').value = e.data.token; }); window.capSolve = function() { document.getElementById('cap-frame').contentWindow .postMessage({ type: 'cap:start' }, window.location.origin); }; })(); </script>
/cap-frame route is registered automatically by the service provider. Its Content-Security-Policy header includes 'unsafe-eval', 'wasm-unsafe-eval', blob:, and img-src data: — everything Cap needs — while frame-ancestors 'self' prevents embedding from external origins.
Token flow:
Parent (strict CSP) iframe /cap-frame (permissive CSP)
│── postMessage(cap:start) ──►│ widget.solve()
│◄── postMessage(cap:token) ──│ e.detail.token
│ fills #cap-frame-token │
Programmatic trigger — @capFrame exposes window.capSolve() on the parent page:
// Trigger Cap resolution from Alpine, Vue, React, etc. window.capSolve(); // Listen for the token (in addition to the hidden input auto-fill) window.addEventListener('message', (e) => { if (e.origin !== window.location.origin) return; if (!e.data || e.data.type !== 'cap:token') return; // e.data.token is ready — pass it to your backend myForm.submit(e.data.token); });
Without nonce (if your CSP does not use nonces):
@capFrame
Customising the route path — set CAP_FRAME_ROUTE in .env:
CAP_FRAME_ROUTE=captcha/frame
CSP nonce support
All directives accept an optional nonce for strict Content Security Policies:
@capConfig(Vite::cspNonce()) @capScripts(Vite::cspNonce()) @cap(Vite::cspNonce())
@cap passes the nonce as data-cap-csp-nonce on the widget element, which Cap uses internally for its workers and inline scripts.
CSP headers
Cap's widget runs WebAssembly locally. A strict CSP must account for this:
Content-Security-Policy:
script-src 'nonce-{nonce}' 'strict-dynamic' 'wasm-unsafe-eval';
connect-src 'self' https://your-cap-instance.example.com;
'wasm-unsafe-eval' — required for the WebAssembly proof-of-work computation.
connect-src — must include your Cap instance origin so the widget can reach /challenge and /redeem.
Instrumentation and strict CSP
Cap's optional instrumentation feature (enabled per site key in the Cap admin dashboard) runs fingerprinting code inside a sandboxed iframe. Since Cap v3.x this code calls
eval()andnew Function(), which are blocked by ascript-srcwithout'unsafe-eval'.If instrumentation is enabled and
'unsafe-eval'is absent from your CSP, the widget will report an[instr_timeout]error and the Cap server will return HTTP 429, making every verification attempt fail.Recommended workaround: use
@capFrame— the widget runs in a dedicated iframe with its own permissive CSP, leaving the parent page CSP strict.Alternative workarounds:
- Disable instrumentation for the site key in the Cap admin dashboard (
PUT /keys/:siteKey/configwith{"instrumentation": false}).- Add
'unsafe-eval'toscript-src(weakens the CSP of the parent page).This is a known upstream issue: tiagozip/cap#268.
Middleware
Protect any route by applying the cap.verify middleware:
Route::post('/contact', [ContactController::class, 'store']) ->middleware('cap.verify');
Returns HTTP 422 with the message Cap verification failed. if the token is missing or invalid.
Validation rule
Use CapRule inside a Form Request or an inline validator:
use LaravelCap\Rules\CapRule; public function rules(): array { return [ 'cap-token' => ['required', new CapRule], // other fields... ]; }
Facade
use LaravelCap\Facades\Cap; if (Cap::verify($request->input('cap-token'))) { // token is valid }
Service (dependency injection)
use LaravelCap\Cap; class ContactController extends Controller { public function __construct(private readonly Cap $cap) {} public function store(Request $request): RedirectResponse { $this->cap->verifyOrFail($request->input('cap-token')); // ... } }
verifyOrFail() throws a CapVerificationException if the token is invalid.
Testing
composer install ./vendor/bin/phpunit
License
MIT
oliweb/laravel-cap 适用场景与选型建议
oliweb/laravel-cap 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 57 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 05 月 07 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「captcha」 「laravel」 「proof-of-work」 「cap」 「spam-protection」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 oliweb/laravel-cap 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 oliweb/laravel-cap 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 oliweb/laravel-cap 相关的其它包
同方向 / 同关键字的高下载量 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.
统计信息
- 总下载量: 57
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 47
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-05-07