danielm/laravel-simple-altcha

ALTCHA (proof-of-work captcha) for Laravel: challenge endpoint, validation rule, middleware and Inertia/React stubs. Built on altcha-org/altcha v2.
14
Install
composer require danielm/laravel-simple-altcha
Latest Version:v0.1.5
PHP:^8.2
License:MIT
Last Updated:Oct 8, 2026
Links: GitHub  ·  Packagist
Maintainer: danielm

Laravel ALTCHA

tests

ALTCHA proof-of-work captcha for Laravel, built on altcha-org/altcha v2. Includes:

  • a challenge endpoint (GET /altcha/challenge)
  • a ValidAltcha validation rule and an altcha route middleware
  • replay protection (a solved payload works once)
  • a React / Inertia component published as a stub

Install

composer require danielm/laravel-simple-altcha
npm install altcha

.env:

# php -r "echo bin2hex(random_bytes(32)), PHP_EOL;"
ALTCHA_HMAC_SECRET=

All supported variables are documented in .env.example.

Optional publishing:

php artisan vendor:publish --tag=altcha-config   # config/altcha.php
php artisan vendor:publish --tag=altcha-lang     # translations
php artisan vendor:publish --tag=altcha-react    # React component + JSX types

The altcha-react tag copies:

  • resources/js/components/altcha-widget.tsx

Backend

Validation rule (implicit, so a missing field also fails):

use Danielm\LaravelSimpleAltcha\Rules\ValidAltcha;

$request->validate([
    'email'  => ['required', 'email'],
    'altcha' => [new ValidAltcha],
]);

Or middleware:

Route::post('/contact', ContactController::class)->middleware('altcha');

Or directly:

$result = app(\Danielm\LaravelSimpleAltcha\AltchaManager::class)->verify($request->input('altcha'));
$result->verified;  // bool
$result->reason;    // missing | malformed | expired | invalid | replayed

Or global middleware (in bootstrap/app.php):

$middleware->web(append: [
    //... other laravel middlewares
    VerifyGlobalAltcha::class,
]);

In this case you must also define ALTCHA_VERIFY_ROUTES to specify what route (names) to check:

ALTCHA_VERIFY_ROUTES=login.store,register.store

Frontend (Inertia + React)

import { useRef } from 'react';
import { useForm } from '@inertiajs/react';
import AltchaWidget, { AltchaHandle } from '@/components/altcha-widget';

export default function Contact() {
    const altcha = useRef<AltchaHandle>(null);
    const { data, setData, post, processing, errors } = useForm({ email: '', altcha: '' });

    const submit = (e: React.FormEvent) => {
        e.preventDefault();
        post('/contact', {
            // payloads are single-use, so always get a fresh one afterwards
            onFinish: () => altcha.current?.reset(),
        });
    };

    return (
        <form onSubmit={submit}>
            <input value={data.email} onChange={(e) => setData('email', e.target.value)} />
            <AltchaWidget ref={altcha} onChange={(payload) => setData('altcha', payload)} />
            {errors.altcha && <p>{errors.altcha}</p>}
            <button disabled={processing || !data.altcha}>Send</button>
        </form>
    );
}

challengeUrl defaults to /altcha/challenge. If you change altcha.route.path, pass the new URL. Extra props are forwarded to <altcha-widget> as attributes.

Configuration notes

  • Algorithm: pbkdf2 (default, no extensions), argon2id (ext-sodium), scrypt (ext-scrypt). cost means different things per algorithm. Your widget build must support the one you choose.
  • Replay protection uses Cache::add(). Use redis/memcached/database in production; the file driver is not atomic. Keys are derived from the challenge's HMAC signature, so re-encoding a payload does not bypass it.
  • Challenge route is intentionally outside the web group (no session or cookies) and throttled by default. Adjust altcha.route.middleware.
  • Widget requires HTTPS (localhost is fine for development).

Testing in your app

Set ALTCHA_TESTING_BYPASS=let-me-in in .env.testing, then post ['altcha' => 'let-me-in']. The bypass only works when the app environment is testing.

Package tests

composer install
composer test

Related Packages

machour/laravel-data-table

A reusable, server-side DataTable system for Laravel + Inertia.js + React (TanSt...

1,949 2
akira/laravel-spectra

Illuminate your API — interactive inspector for Laravel 12 with Inertia + React

13 4
samushi/domion

Domion - Professional Architecture for Laravel with Automated Frontend & Auth In...

68 0
yogijowo/laravel12-react-starterkit

Starter Kit Laravel 12 + React + Inertia + Tailwind + ShadCN UI + Spatie Permiss...

481 175

Version History

Version Released PHP Laravel License
v0.1.5 ^8.2 ^12.0|^13.0 MIT
v0.1.4 ^8.2 ^12.0|^13.0 MIT
v0.1.3 ^8.2 ^12.0|^13.0 MIT
v0.1.2 ^8.2 ^12.0|^13.0 MIT
v0.1.1 ^8.2 ^12.0|^13.0 MIT