cluion / turing
Self-hosted, zero-dependency, cross-language modern captcha with a Laravel integration.
Requires
- php: >=8.2
- ext-hash: *
- ext-json: *
- ext-mbstring: *
- illuminate/support: ^10.0 || ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
README
English · 繁體中文 · 📖 Documentation
A signed challenge the server issues, a client widget that solves it in the browser with native Web Crypto, and a token the enclosing form submits for the server to verify. No external service, no tracking. Three layers: a framework-agnostic PHP Core, framework integrations (Laravel first), and a JS client stack.
Install
Laravel (PHP)
composer require cluion/turing
php artisan vendor:publish --tag=turing-config # then set TURING_SECRET
JavaScript
| Package | Install | Use |
|---|---|---|
@cluion/turing-core |
pnpm add @cluion/turing-core |
Headless core (plain / any framework) |
@cluion/turing-element |
pnpm add @cluion/turing-element |
<turing-captcha> Web Component |
@cluion/turing-vue |
pnpm add @cluion/turing-vue |
<Turing> Vue 3 component |
@cluion/turing-react |
pnpm add @cluion/turing-react |
<Turing/> React component |
Plain HTML via CDN — pin an exact version and add Subresource Integrity:
<script src="https://cdn.jsdelivr.net/npm/@cluion/turing-core@1.0.0/dist/turing.global.js" integrity="sha384-Yr0+3DwmN0hpeaF/LmMvcDSdUptv057stCTNWChQQjcYU/L7JbZFBkbe2BEg6/1D" crossorigin="anonymous" defer></script>
Usage
Laravel — one line to show, one line to verify
<form method="post" action="/submit"> @csrf <x-turing type="pow" /> <button type="submit">Send</button> </form>
$request->validate(['turing_token' => 'required|turing']); // or: Turing::verifyRequest($request);
The <x-turing/> component renders a CSP-safe container the client widget mounts
onto. The widget fetches the challenge from data-turing-url, solves the PoW
with native Web Crypto (no WASM), and injects a hidden turing_token for the
form to submit.
JavaScript
import '@cluion/turing-core'; // auto-mounts every [data-turing] container
A runnable plain-HTML page is in
examples/plain-html/index.html. Framework
guides: Laravel · Plain HTML
· Vue · React ·
Security.
Core
Framework-agnostic PHP core under php/src/Core. Token =
base64url(payload).base64url(signature) with canonical (sorted-key) JSON.
HMAC-SHA256 default signing (Ed25519 opt-in). Challenge types: math, text,
pow (PBKDF2-SHA256 default, SHA-256 leading-zero-bit opt-in). Stateless by
default; single-use via a Store.
Cross-language vectors
php/tests/vectors/ is the wire contract — the
authoritative wire-contract reference quotes
them verbatim (a docs build guard fails on drift). Language ports MUST reproduce
these fixtures exactly (token bytes, PoW counters, answer hashes). Binary payload
fields (e.g. keySignature) are always base64url of the raw bytes.
Development
composer install vendor/bin/phpunit # PHP: Core + Laravel suites (Laravel 10–13 in CI) cd js && pnpm install pnpm -r build && pnpm -r test # JS: 4 packages (build before test) pnpm -r typecheck cd ../docs && pnpm docs:build # docs site + vector drift guard cd ../e2e && pnpm exec playwright test # browser round-trip (needs chromium)
License
MIT.