Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Altcha Bundle Laravel Package

huluti/altcha-bundle

Symfony bundle that integrates ALTCHA, a privacy-friendly proof-of-work CAPTCHA, into your forms. Protect against spam and bots without cookies, tracking, or third-party services, with simple form type and validator support.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're actually adopting. huluti/altcha-bundle is a Symfony bundle. There is no AltchaServiceProvider, no facade, no publishable config, no Blade views. Composer will happily install it into a Laravel app (Symfony components are standalone), but nothing in it will boot: the services live in a services.yaml you never load, the routes are Symfony attributes, the widget is Twig, and the validation is a Constraint/ConstraintValidator pair bound to the Symfony Form and Validator components. For a Laravel codebase the bundle is reference material, not a dependency. As of 3.1.0 this is still true — the two changes in that release (a type-exception fix in #71, and CSP support in #73) are both Symfony-side concerns and don't move the needle on a Laravel integration path.

What to copy from it. Altcha itself is a proof-of-work CAPTCHA: the browser solves a small SHA-256 puzzle (a few hundred ms of CPU) and submits a base64 payload; the server verifies an HMAC signature over the solution. The bundle's job is to wire four things:

  1. A challenge endpoint that returns {algorithm, challenge, salt, maxnumber, signature}.
  2. A widget component that renders <altcha-widget challengeurl="..."> and writes the payload into a hidden altcha input.
  3. A server-side verifier that decodes the payload and checks the HMAC.
  4. A config tree (altcha.hmac_key, enable/disable flag, widget options).

Read the bundle's src/ in that order — DependencyInjection/Configuration.php first (it tells you the full option surface), then the controller, then the constraint validator. Everything there maps 1:1 onto a Laravel route, a Blade component, a Rule object, and config/altcha.php.

Minimal path for Laravel. Skip the bundle. Use the official PHP implementation (altcha-org/altcha on Packagist — verify the current name before committing) plus the widget from npm/CDN:

composer require altcha-org/altcha
npm i altcha   # or load the <script> from a CDN
php artisan make:controller AltchaChallengeController
// routes/web.php
Route::get('/altcha/challenge', AltchaChallengeController::class)
    ->middleware('throttle:60,1')
    ->name('altcha.challenge');
// config/altcha.php
return [
    'enabled'  => env('ALTCHA_ENABLED', true),
    'hmac_key' => env('ALTCHA_HMAC_KEY'),
];

ALTCHA_HMAC_KEY should be a long random string, distinct from APP_KEY, and must be identical on every node that both issues and verifies challenges.

First use case: put it on the one form that gets hammered but doesn't warrant a vendor CAPTCHA — contact, newsletter signup, password reset request, guest checkout. Because there are no third-party calls, it's the drop-in answer when legal or a customer objects to Google reCAPTCHA. Add it there first, measure drop-off, then decide about wider rollout.

Implementation Patterns

Blade component + validation rule. Treat the Symfony FormType as a Blade component and the Symfony Constraint as a Rule:

{{-- resources/views/components/altcha.blade.php --}}
@if (config('altcha.enabled'))
    <altcha-widget challengeurl="{{ route('altcha.challenge') }}"
                   name="altcha"
                   auto="onload"></altcha-widget>
    @push('scripts')<script type="module" src="{{ Vite::asset('resources/js/altcha.js') }}"></script>@endpush
@endif
class AltchaRule implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! config('altcha.enabled')) return;

        try {
            $ok = (new AltchaVerifier(config('altcha.hmac_key')))->verifySolution((string) $value);
        } catch (\Throwable $e) {
            report($e);
            $ok = false;
        }

        if (! $ok) $fail('Please complete the verification challenge.');
    }
}

Then $request->validate(['altcha' => [new AltchaRule]]) in a FormRequest. Keep it out of the global middleware stack — you rarely want it on APIs or JSON clients.

Two-tier protection. Pair Altcha with rate limiting rather than replacing it. The challenge endpoint is unauthenticated and generates an HMAC per request; throttle it. The proof-of-work is deliberately cheap so that legitimate users aren't blocked — it raises the cost of bulk submission, it does not stop a determined attacker with a headless browser.

Cross-framework org pattern. If your company runs a Symfony app (using this bundle) and a Laravel app side by side, share hmac_key between them. Verification is stateless and framework-agnostic — a payload minted by the Symfony challenge endpoint validates fine in Laravel and vice versa. This is the one genuinely useful way the bundle shows up in a Laravel developer's day. Note that challenge payloads are short-lived, so cross-app use only makes sense when the widget and the form live in the same page.

Livewire / Inertia. The Altcha widget is a web component. After a Livewire morph, or after Inertia swaps the page component, the element may need reset() and the challenge re-fetched — otherwise you post a stale solution. For Livewire, add wire:ignore around the widget and reset it in the submit/reset hooks.

Feature-flag it. Mirror the bundle's enabled config so you can turn the challenge off in local dev, in CI, and during incident response without a deploy of code paths. Default it off in phpunit.xml.

Testing. Never exercise the real proof-of-work in a feature test. Bind a fake verifier in the container (or set altcha.enabled=false) and assert the rule is wired, that a false verifier yields a 422, and that the payload is pulled from the expected input name.

Gotchas and Tips

Do not put the bundle in a Laravel app. It pulls symfony/form, symfony/twig-bundle, and friends for no benefit; you'd have to hand-construct every service. If you truly need it, you're already writing new ConstraintValidator(...) by hand and have lost the reason to install it. Fork-and-port beats install-and-shim.

Signature mismatches are almost always key drift. The challenge endpoint and the verifier must use byte-identical keys. Cached config (php artisan config:cache) is the usual culprit: a deploy that rotates ALTCHA_HMAC_KEY without flushing config leaves the issuer on the new key and the verifier on the old one. Rotate keys with care — every in-flight challenge becomes invalid.

The HMAC check does not check expiry. Altcha's signature covers the solution, not the clock. If you encode an expiry into the salt (the bundle's config exposes salt length/behaviour — read it), you must validate that expiry yourself in the rule. Otherwise a solved payload is valid forever.

Replay protection is on you. Verification is stateless, so the same base64 payload can be submitted repeatedly. If the form is sensitive (checkout, registration), store a hash of the payload in the cache with a short TTL and reject duplicates. The bundle does not do this for you, and neither will a naive port.

Debugging a failing submit. The widget writes a long base64 string into the hidden altcha input. Dump it, base64-decode, and read the JSON — that immediately tells you whether the widget ran at all, which algorithm the browser picked (default SHA-256), and whether the challenge fields look sane. A missing input almost always means the widget script didn't load (CSP blocking the module, or the challengeurl route returning the login redirect because of session middleware — remember to exclude it from auth). If you upgraded the bundle to 3.1.0 hoping #71 fixed a "type exception" you saw in logs, note the fix is in the Symfony-side widget/constraint code — nothing observable changes for a Laravel app that never loaded the bundle in the first place.

CSP and asset loading. The script must come from a source you've allowlisted. If you self-host altcha.js through Vite, you avoid a CSP exception and a third-party dependency entirely — which is the point of choosing this over reCAPTCHA. Version 3.1.0 added CSP support to the Symfony widget (via #73), which is welcome news for anyone using the bundle directly in Symfony — but it's a Twig-attribute / widget-render concern, not something that reaches into a Laravel Blade component. The self-host-via-Vite advice above remains the correct Laravel-side equivalent, and is arguably simpler than what the bundle does.

Custom algorithms must be implemented twice. The bundle allows swapping the hashing algorithm; the widget must be told the same one. Getting this half-right produces challenges that solve instantly and verify never.

Vendor risk is real here. The project is small, effectively one maintainer, with a thin dependent graph — but it is actively shipping (3.0.0 → 3.1.0 brought a bug fix and CSP support in the same release), so "unmaintained" is the wrong worry; "low bus factor" is the right one. Budget for reading the code rather than the docs, pin the PHP library version, and keep your Laravel wrapper thin enough (one controller, one rule, one Blade component) that swapping the underlying implementation is an afternoon, not a refactor.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route