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

Securimage Laravel Package

dapphp/securimage

Securimage is a PHP CAPTCHA library that generates customizable captcha images and audio challenges to protect forms from spam. It supports custom fonts, colors, backgrounds, wordlists, and audio captchas, and integrates easily into existing PHP applications and frameworks.

Deep Wiki
Context7

Getting Started

Securimage is a plain-PHP CAPTCHA library, not a Laravel package. There is no service provider, facade, config publish, or auto-discovery. You wire it up yourself.

1. Install and verify requirements

composer require dapphp/securimage

Then verify:

  • PHP version compatibility. The last release is from 2020, so test on your target PHP (especially 8.1+ for deprecation warnings).
  • GD with FreeType support: gd_info()['FreeType Support'] must be true.
  • A writable/readable location for fonts and audio assets.
  • Either PHP native sessions ($_SESSION) or a PDO database, depending on the storage mode you choose.

Confirm how the class is autoloaded:

// Usually a global class, not namespaced
$captcha = new \Securimage();

Check vendor/composer/autoload_classmap.php if unsure; older releases often use classmap autoloading rather than PSR-4.

2. Publish/reference assets

The package ships fonts and audio files inside vendor/dapphp/securimage/. In Laravel, the safest approach is to reference absolute vendor paths from config rather than trying to asset() them:

// config/securimage.php
return [
    'font_file'   => base_path('vendor/dapphp/securimage/fonts/DejaVuSans.ttf'),
    'audio_path'  => base_path('vendor/dapphp/securimage/audio'),
    'image_width' => 215,
    'image_height' => 80,
    'code_length' => 6,
    'expiry_time' => 900,
    'charset'     => 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789',
];

If you copy assets into storage/ or public/, use absolute paths in config; Securimage does not resolve Laravel paths for you.

3. Add routes and a controller

// routes/web.php
Route::get('/captcha/image', [CaptchaController::class, 'image'])
    ->name('captcha.image')
    ->middleware('throttle:60,1');

Route::get('/captcha/audio', [CaptchaController::class, 'audio'])
    ->name('captcha.audio')
    ->middleware('throttle:30,1');

A minimal image action:

public function image()
{
    $captcha = new \Securimage(config('securimage'));

    ob_start();
    $captcha->show();
    $body = ob_get_clean();

    return response($body, 200, [
        'Content-Type'  => $captcha->getMimeType(),
        'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
        'Pragma'        => 'no-cache',
    ]);
}

If your version of show() calls exit, you cannot buffer it inside a normal Laravel response. In that case, either use a dedicated route that ends with exit, or switch to a method/API that returns the image bytes.

4. Put it on a form

<form method="POST" action="{{ route('register') }}">
    @csrf

    <img src="{{ route('captcha.image') }}?t={{ now()->timestamp }}"
         alt="CAPTCHA image"
         id="captcha-image">

    <button type="button" onclick="document.getElementById('captcha-image').src='{{ route('captcha.image') }}?t=' + Date.now()">
        Refresh
    </button>

    <input type="text" name="captcha_code" autocomplete="off" required>

    @error('captcha_code')
        <span>{{ $message }}</span>
    @enderror
</form>

Where to look first

  • vendor/dapphp/securimage/examples/ contains the canonical securimage_show.php and securimage_play.php scripts. Treat those as the reference implementation.
  • The top of securimage.php documents the full option array.
  • Look at the storage/PDO examples if you want to avoid PHP native sessions entirely.

First real use case: protect registration, login, password reset, or a public contact form. Start with the image endpoint plus a Laravel validation rule, then add audio for accessibility.


Implementation Patterns

Pattern 1: A custom Laravel validation rule

This is the cleanest day-to-day integration. Encapsulate Securimage validation so controllers and form requests stay framework-idiomatic.

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Securimage;

class ValidCaptcha implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! config('securimage.enabled', true)) {
            return;
        }

        $captcha = new Securimage(config('securimage'));

        if (! $captcha->check((string) $value)) {
            $fail('The CAPTCHA code is incorrect or has expired.');
        }
    }
}

Usage:

$request->validate([
    'name'         => ['required', 'string', 'max:255'],
    'email'        => ['required', 'email'],
    'captcha_code' => ['required', 'string', new ValidCaptcha],
]);

For Laravel versions before 10, implement Rule with passes() and message() instead.

Pattern 2: Stateless validation with a CAPTCHA ID and database storage

Default Securimage stores the expected code in $_SESSION. That is awkward in Laravel because Laravel's session driver does not populate PHP's native $_SESSION. If you run multiple app servers, native session storage is also unreliable.

The more Laravel-friendly pattern is Securimage's CAPTCHA ID plus database storage:

  • Enable use_database, configure pdo_dsn, pdo_user, pdo_pass, and the table.
  • Render the image with a captchaId query parameter.
  • Put that same captchaId in a hidden form field.
  • Validate with Securimage::checkByCaptchaId($captchaId, $value) or the instance equivalent, depending on your version.

Sketch:

// Controller: image
$captcha = new \Securimage(array_merge(config('securimage'), [
    'use_database' => true,
    'captchaId'    => $request->string('captcha_id')->toString() ?: null,
]));
<input type="hidden" name="captcha_id" value="{{ $captchaId }}">
<img src="{{ route('captcha.image', ['captcha_id' => $captchaId]) }}">

Use the SQL schema shipped with the package (look under its database/ or examples/ directory) and create a migration for it. This gives you one-time codes, expiry checks, and load-balancer safety without depending on PHP sessions.

Pattern 3: Bridging PHP native sessions

If you stick with session storage, you must make $_SESSION work independently of Laravel's session. Do this on both the image route and the validation path:

if (session_status() === PHP_SESSION_NONE) {
    session_name('SECURIMAGE');
    session_start();
}

$captcha = new \Securimage(config('securimage'));

Be careful: this creates a second session cookie (SECURIMAGE) alongside Laravel's session cookie. That is acceptable if scoped and documented, but it is fragile under Octane, serverless, and multi-node deployments. Prefer database storage for anything beyond a single-server app.

Pattern 4: Refresh and cache-busting

CAPTCHA images are frequently cached by browsers or proxies. Always add a unique query parameter when refreshing:

const img = document.getElementById('captcha-image');
img.src = `${img.dataset.src}?t=${Date.now()}`;

If you use captchaId, generate a new ID on each refresh so the server stores a fresh code instead of overwriting or reusing a stale one.

Pattern 5: Audio accessibility endpoint

Wire a separate route for audio:

public function audio()
{
    $captcha = new \Securimage(config('securimage'));

    ob_start();
    $captcha->outputAudioFile();
    $body = ob_get_clean();

    return response($body, 200, [
        'Content-Type'  => 'audio/wav',
        'Cache-Control' => 'no-store',
    ]);
}

Provide the audio alternative next to the image. CAPTCHA-only visual challenges create accessibility and compliance problems.

Pattern 6: Blade component

Wrap the image, input, refresh button, and validation error into one reusable component:

{{-- resources/views/components/captcha.blade.php --}}
<div class="captcha">
    <img src="{{ route('captcha.image') }}?t={{ now()->timestamp }}"
         alt="CAPTCHA"
         id="captcha-image">
    <button type="button" onclick="document.getElementById('captcha-image').src='{{ route('captcha.image') }}?t=' + Date.now()">
        New code
    </button>
    <a href="{{ route('captcha.audio') }}" target="_blank">Audio CAPTCHA</a>

    <input type="text" name="captcha_code" required autocomplete="off">
    @error('captcha_code') <span>{{ $message }}</span> @enderror
</div>

Pattern 7: Disable CAPTCHA in tests and local development

Do not scrape the image in tests. Add an application-level switch:

// config/securimage.php
'enabled' => env('SECURIMAGE_ENABLED', true),

Then short-circuit in the rule:

if (! config('securimage.enabled', true)) {
    return;
}

Set SECURIMAGE_ENABLED=false in phpunit.xml and local .env. Alternatively, bind a fake validator implementation in the container.

Pattern 8: Rate limiting and abuse controls

CAPTCHA is not a substitute for rate limiting. Apply throttle middleware to both the image and audio routes, and consider limiting by IP and route. CAPTCHA endpoints are themselves expensive because they generate images and audio.


Gotchas and Tips

Maintenance and PHP compatibility

The last release was 2020-05-30. Treat it as stable but effectively unmaintained. Expect deprecation notices on PHP 8.1+ and test carefully. If you hit fatal errors, the practical options are:

  • Pin the package and patch locally.
  • Fork it into your organization.
  • Wrap instantiation in a small adapter class so you can swap implementations later.

Do not assume modern PHP compatibility or active security patches.

$_SESSION does not mean Laravel session

This is the biggest integration trap. Securimage writes codes to $_SESSION, but Laravel's file/database/redis session driver does not populate PHP's native $_SESSION array. You will see a working image but validation that always fails if you do not bridge sessions or use database storage.

Symptoms:

  • Image renders correctly.
  • check() always returns false.
  • $_SESSION['securimage_code_value'] is missing in the validation request.

Fix by choosing one storage strategy explicitly: native session bridge, PDO storage with captchaId, or a custom storage adapter.

One-time use and expiry

A successful validation normally removes the code, and codes expire after expiry_time seconds (default around 900). This is correct behavior, but it means:

  • A failed validation may require generating a fresh image.
  • A slow form submission may expire the code.
  • Refreshing the image server-side invalidates the previous code.

Surface a clear "code expired, request a new one" message rather than a generic invalid-code error.

Case sensitivity

Check the case_sensitive option. The default is typically false, but if it is enabled anywhere in your config, users will be confused by case failures. Pick one behavior and document it.

Absolute paths

font_file and audio_path must be real, readable absolute paths. Relative paths and Laravel asset() URLs do not work. When deploying to a container or Forge/Envoyer release directory, base_path() changes on each release, so do not hard-code paths.

Output buffering and headers

Image endpoints commonly break because:

  • Laravel or middleware has already sent headers.
  • show() emits its own Content-Type.
  • Debug output or a PHP notice is printed before the image bytes, corrupting the image.
  • exit inside show() bypasses Laravel's response lifecycle.

Use ob_start()/ob_get_clean() where possible, set explicit headers, and ensure APP_DEBUG output cannot leak into the binary response. If the library calls exit, isolate it in a route that intentionally ends the request.

Caching and proxies

Set Cache-Control: no-store, no-cache, must-revalidate on CAPTCHA responses. Exclude /captcha/* from CDN caching and from any HTTP cache middleware. A stale CAPTCHA image paired with a fresh stored code is a classic production bug.

captchaId input validation

If you accept a captchaId from the request, validate it strictly, e.g. alpha_dash and a maximum length. Even if the package uses prepared statements, rejecting malformed IDs early avoids obscure failures and reduces abuse surface.

Octane and long-running workers

Under Laravel Octane, static class state and native $_SESSION are risky. Prefer the database/captchaId flow, close native sessions after use, and avoid caching Securimage instances in singletons.

Multiple CAPTCHAs on one page

If two forms each need a CAPTCHA, use separate namespaces or separate captchaId values. Otherwise the second image overwrites the first code and one form will always fail.

Audio path and permissions

The audio CAPTCHA requires the bundled .wav files and a readable audio_path. In containerized deployments, confirm the files were not excluded by .gitignore or Docker build rules. Also check that the generated audio response has the correct MIME type.

Error handling

Image generation can fail because of missing GD, missing fonts, or permission issues. A raw PHP warning can turn a CAPTCHA response into an HTML error page that the browser tries to render as an image. Wrap generation in a try/catch or check prerequisites at boot, and log the real error rather than emitting it.

Do not log or persist the expected code

Avoid logging the generated code, the captchaId plus code together, or full validation payloads. CAPTCHA values are short-lived secrets.

Accessibility and UX

Always provide an audio alternative and a visible refresh control. Do not make the CAPTCHA the only barrier between a user and account recovery. Keep code length moderate; an overly distorted 8-character code creates support burden without meaningfully improving bot resistance.

Extension points

The practical customization points are:

  • Subclassing Securimage to override code generation or rendering behavior.
  • Configuring fonts, colors, lines, distortion, and image size via the options array.
  • Using PDO-backed storage for stateless deployments.
  • Wrapping the library in your own CaptchaService so validation, testing, and future replacement are centralized.

Given the package's age, keep the integration surface small and behind an adapter. That keeps a future migration to a maintained CAPTCHA package from touching every controller and form.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager