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

Captcha Challenge Bundle Laravel Package

tourze/captcha-challenge-bundle

tourze/captcha-challenge-bundle is a PHP/Symfony bundle that adds configurable captcha challenge generation and verification to your app, helping protect forms and APIs from automated abuse with a simple, framework-friendly integration.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is. tourze/captcha-challenge-bundle sits in the Tourze PHP ecosystem, which is Symfony-first. Despite the -bundle suffix, the value you actually consume day-to-day is the small set of challenge/validator classes — the bundle wiring is only relevant if you're on Symfony. In a Laravel app you'll typically pull in the package and use its core classes directly, registering the glue yourself.

Minimal setup.

composer require tourze/captcha-challenge-bundle

Then read, in this order:

  1. src/ — look for the challenge abstraction (something like ChallengeInterface), the generator, and the validator/verifier service. That trio is the whole mental model.
  2. tests/ — the fastest way to learn the intended call sequence, because at 0.0.x there is effectively no prose documentation.
  3. composer.json — check the PHP constraint and whether it drags in Symfony components (framework-bundle, http-foundation, dependency-injection) that you'll need to account for.

First use case. The canonical loop is: issue → render → submit → verify.

  • On GET /form, call the generator to produce a challenge, store the challenge id (plus any answer state) in the session or a cache entry keyed by that id, and return a payload the frontend can render (image/slider/click-coordinates, depending on the challenge type shipped).
  • On POST /form, take the id + the user's response, call the validator, and treat a false result exactly like a validation error — reject the request.

Get that single round-trip working end to end before touching configuration. At this version there is very little to configure, and almost every problem you'll hit is in the handoff between issue and verify.

Implementation Patterns

Wrap it, don't scatter it. Put both calls behind one small service (CaptchaService) with issue(): ChallengePayload and verify(string $id, mixed $response): bool. The package API is pre-1.0 and will move; a single seam means the churn is a one-file change instead of a grep across controllers.

Bind the challenge to the actor and the route. A challenge id that any client can submit is a bypass waiting to happen. Store the issued id in the session (or in a cache entry keyed by user + purpose) and, on verify, assert the submitted id matches the one you issued for this session and this purpose ("signup", "password-reset", "checkout"). This is the single highest-value thing you can add on top of the package.

Use a FormRequest rule. Model verification as a validation rule rather than an inline if, so it composes with the rest of your error handling:

'captcha_id'       => ['required', 'string'],
'captcha_response' => ['required', 'string'],

and in withValidator(), fail the attribute when verify() returns false. You get correct 422 behaviour, error-bag entry, and Inertia/Blade repopulation for free.

Make it single-use. Consume the challenge on any verification attempt — success or failure — and re-issue on the response. Without this, a fixed response can be retried indefinitely and the challenge degrades to a non-obstacle.

Pair it with rate limiting. Captchas are an anti-automation friction layer, not an authorisation layer. Keep the throttle middleware on the same route; the captcha reduces bot throughput, the limiter caps the damage when the captcha is solved by a farm.

Give yourself a test double. Bind an always-pass implementation of your CaptchaService interface in the test environment so feature tests for signup/checkout don't have to solve puzzles. Then write two or three dedicated tests against the real implementation for the issue→verify→replay-denied path. That's the split that keeps the rest of the suite cheap.

Clean up issued challenges. Whatever store you use (session, cache, table) needs an expiry — a TTL on the cache entry, or a scheduled prune if you went with a table. Otherwise abandoned form loads accumulate silently.

Gotchas and Tips

The 0.0.x version number is the headline. No semver guarantee, no deprecation cycle. Pin an exact version ("tourze/captcha-challenge-bundle": "0.0.3") and read the diff before bumping. The 0.0.3 release notes are just a compare link with no prose — treat it as an unreviewed change and diff 0.0.2...0.0.3 yourself before upgrading, particularly if you're mid-project.

Symfony auto-discovery does not apply in Laravel. There is no service provider to auto-register and no Laravel facade. You instantiate the classes yourself or bind them in your own provider. Don't waste time looking for a config/captcha-challenge.php — if the package ships configuration at all, it's Symfony bundle config (YAML) and won't be read by Laravel.

Never ship the answer to the client. Whatever the payload from the generator contains, audit it before returning it. If the challenge includes a solution, a position, or a token that the validator trusts blindly, the whole mechanism is decorative. Inspect the actual JSON, don't assume.

CSRF and captcha are independent concerns. Order them deliberately: CSRF first (cheap, rejects forged origins), captcha second (expensive, rejects bots). If you put the captcha first you pay the verification cost for requests that were never going to be accepted.

Expiry is a UX trap, not just a security one. A five-minute challenge window combined with a two-minute form means real users fail. Make the TTL comfortably longer than your slowest legitimate form, and return a distinguishable error for "expired" versus "wrong answer" so the frontend can re-issue a fresh challenge silently rather than accusing the user of being a robot.

Debugging: log the challenge id, never the response. When verification unexpectedly fails in staging, the id is what lets you correlate the issued and submitted sides. Logging the user's response is a privacy problem and clutters logs.

Extension points are the classes, not config. The realistic way to change behaviour at this maturity level is to implement your own generator/validator against the package's interface, or decorate the built-in one — not to hunt for a setting. If you need a different challenge type, expect to write it and wire it in your container binding. That's also your insurance policy if the package stalls: keeping the interface boundary in your own CaptchaService means swapping to another library later costs you one class.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package