geekk/multi-captcha

Various captchas that support the mechanism for switching between them in the configuration file

Maintainers

Package info

github.com/geekk-net/multi-captcha

pkg:composer/geekk/multi-captcha

Statistics

Installs: 23 563

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

1.4.0 2026-03-04 07:03 UTC

This package is auto-updated.

Last update: 2026-03-04 07:03:47 UTC


README

Common php class interfaces for captchas. And various captchas implementation. You can use it for a switching of captcha's type in your project.

Supported types of captcha

Now packages supports these types:

  • Google ReCaptcha v2
  • HCaptcha
  • KCaptcha
  • Cloudflare Turnstile
  • Gregwar (image captcha, based on gregwar/captcha)

You can add new one type. You need implement CaptchaInterface and CaptchaRequestInterface.

Frameworks support

This package didn't tie with any framework. It doesn't work with specific framework's classes or interfaces, for example Illuminate\Http\Request for http requests.

But you can ease create needed wrappers and factories for a working your framework and configuration files.

For Laravel, you can use the geekk/multi-captcha-laravel package.

Installation

Install package:

composer require geekk/multi-captcha

Using

Configuration array:

$config = [
        'default' => 'hcaptcha',

        'connections' => [

            'recaptcha2' => [
                'driver' => 'recaptcha2',
                'site_key' => '...',
                'secret_key' => '...'
            ],

            'hcaptcha' => [
                'driver' => 'hcaptcha',
                'site_key' => '...',
                'secret_key' => '...'
            ],

            'kcaptcha' => [
                'driver' => 'kcaptcha',
                'show_credits' => false
            ],

            'gregwar' => [
                'driver' => 'gregwar',
                // optional: width, height, length, quality, allowed_symbols
            ],

            'turnstile' => [
                'driver' => 'turnstile',
                'site_key' => '...',
                'secret_key' => '...'
            ]
        ]
]

If you plan to use KCaptcha, you need implement storage class:

class CaptchaStore  implements CaptchaStoreInterface {

    protected $store;
    protected $prefix;
    protected $seconds;

    public function __construct(Repository $store, $prefix = 'kcaptcha:', int $seconds = 5*60)
    {
        $this->store = $store;
        $this->prefix = $prefix;
        $this->seconds = $seconds;
    }

    protected function getStoreKey($key)
    {
        return "$this->prefix:{$key}";
    }

    public function getValue(?string $key = null): ?string
    {
        $value = $this->store->get($this->getStoreKey($key));
        $this->store->forget($this->getStoreKey($key));
        return $value;
    }

    public function setValue(string $value, ?string $key = null)
    {
        $this->store->put($this->getStoreKey($key), $value);
    }
}

where Repository is some cache repository. Or you can use session instead of cache.

Implement the CaptchaManager - factory class:

use SomeNamespace/Request; // Request which you use

class CaptchaManager
{

    protected $config;

    protected $connectionName;

    protected $connectionConfig;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    private function loadDriverConfig()
    {
        $this->connectionName = $this->config['default'];
        $this->connectionConfig = $this->config['connections'][$this->connectionName];
    }

    public function getCaptcha(): CaptchaInterface
    {
        $this->loadDriverConfig();
        $driverName = $this->connectionConfig['driver'];
        switch ($driverName) {
            case 'recaptcha2':
                return new ReCaptcha2($this->connectionConfig);
            case 'hcaptcha':
                return new HCaptcha($this->connectionConfig);
            case 'kcaptcha':
                $store = new CaptchaStore();
                return new KCaptcha($this->connectionConfig, $store);
            case 'gregwar':
                $store = new CaptchaStore();
                return new Gregwar($this->connectionConfig, $store);
            case 'turnstile':
                return new Turnstile($this->connectionConfig);
        }
        throw new \Exception(sprintf('Unknown captcha driver: %s', $driverName));
    }

    public function getRequest(Request $request): CaptchaRequestInterface
    {
        $driverName = $this->connectionConfig['driver'];
        switch ($driverName) {
            case 'recaptcha2':
                return new ReCaptcha2Request(count($request->post()), $request->post(ReCaptcha2Request::RESPONSE_NAME), $request->ip());
            case 'hcaptcha':
                return new HCaptchaRequest(count($request->post()), $request->post(HCaptchaRequest::RESPONSE_NAME), $request->ip());
            case 'kcaptcha':
                return new KCaptchaRequest(count($request->post()), $request->post(KCaptchaRequest::RESPONSE_NAME), $request->post(KCaptchaRequest::KEY_NAME));
            case 'gregwar':
                return new GregwarRequest(count($request->post()), $request->post(GregwarRequest::RESPONSE_NAME), $request->post(GregwarRequest::KEY_NAME));
            case 'turnstile':
                return new TurnstileRequest(count($request->post()), $request->post(TurnstileRequest::RESPONSE_NAME), $request->ip());
        }
        throw new \Exception(sprintf('Unknown captcha driver: %s', $driverName));
    }
}

Alternative: single request via CaptchaRequest

If the frontend uses the same component for all captcha types (e.g. a Vue component with unified field names), you can avoid distinguishing request type and always create a CaptchaRequest. Form field names are defined once (e.g. captcha-response):

use Geekk\MultiCaptcha\CaptchaRequest;

public function getRequest(Request $request): CaptchaRequestInterface
{
    // For recaptcha2, hcaptcha, turnstile
    $submitted = (bool) count($request->post());
    $response = $request->post('captcha-response');
    $context = $request->ip();
    return new CaptchaRequest($submitted, $response, $context);
}

Note: for KCaptcha and Gregwar the context is the store key (sent from the frontend), not the IP, so with this approach either do not use KCaptcha/Gregwar or handle them in a separate branch (e.g. a separate if by driver).

Then you can use it

$captchaManager = new CaptchaManager($config);

$captcha = $captchaManager->getCaptcha();

// Render captcha in template
echo $captcha->render();

// Verify user's response
$result = $captcha->verify($captchaManager->getRequest($request));

Customising captcha's view

Use css for a customizing.

For captcha's templates generated on frontend side you can get data from method CaptchaInterface::getViewData().