# Captcha

`hostkurd/flocms-captcha` adds an image captcha to forms that guests can submit, such as a contact or sign-up form, to keep automated spam out. New FloCMS projects already require it. It needs PHP's GD extension.

## How it works

1. A page shows an image with a random code, for example `K7MPQ2`. Creating the image stores the code in the visitor's session.
2. The visitor types the code into the form.
3. Your controller compares the typed text with the stored code.

## Showing the image

Serve the image from a controller action, so it runs with the site's session:

```php
<?php
namespace FloCMS\Controllers;

use FloCMS\Captcha\CaptchaManager;
use FloCMS\Core\Controller;

class CaptchaController extends Controller
{
    public function image(): void
    {
        (new CaptchaManager())->output();
        exit;
    }
}
```

`output()` creates a new code, stores it, and sends a PNG with headers that stop browsers from caching it. The `exit` matters: the action has no view to render.

In the form:

```html
<form method="post" action="">
    @csrf
    <img src="<?= SITE_URI ?>/captcha/image" alt="Security code" width="180" height="50">
    <input type="text" name="captcha" autocomplete="off" required>
    <button type="submit">Send</button>
</form>
```

A "new code" link only needs to reload the image, for example by adding `?` and a timestamp to its `src`.

## Checking the code

```php
use FloCMS\Captcha\CaptchaManager;

public function contact(): void
{
    if (!$this->request->isMethod('POST')) {
        return;
    }

    $captcha = new CaptchaManager();
    $input = $this->request->input('captcha');
    $valid = $captcha->verify(is_string($input) ? $input : null);

    if (!$valid) {
        $this->data['error'] = 'The security code is wrong. Please try again.';
        return;
    }

    // handle the form
}
```

`verify()` returns `true` when the typed text matches. Spaces around it are ignored, and by default upper and lower case don't matter.

Each code can be checked **once**. `verify()` removes the stored code on every check, right or wrong, so a script can't keep guessing the same code and a submitted form can't be replayed. After a failed check, the form needs a new image, which it gets when the page shows the form again. Check once per request and keep the result; a second `verify()` call fails.

> [!NOTE]
> Single-use codes arrived in flocms-captcha 1.1.0 (FloCMS 1.7.5). With 1.0.0, `verify()` kept the code, so you had to call `clear()` after every check. Calling `clear()` after `verify()` still works; it's just no longer needed.

A captcha doesn't replace CSRF protection; keep `@csrf` in the form. See [sessions and CSRF](/docs/2.0/session#csrf-protection).

## Configuration

Pass the settings you want to change. Use the same settings for `output()` and `verify()`, or at least the same `session_key`:

```php
$captcha = new CaptchaManager([
    'length' => 5,
    'width' => 200,
    'height' => 60,
]);
```

| Setting | Default | |
|---|---|---|
| `length` | `6` | Number of characters |
| `width`, `height` | `180`, `50` | Image size in pixels |
| `font_size` | `24` | |
| `angle_min`, `angle_max` | `-15`, `15` | Rotation range of each character |
| `noise_lines` | `8` | Lines drawn across the image |
| `characters` | `ABCDEFGHJKLMNPQRSTUVWXYZ23456789` | The characters to pick from; easily confused ones like `0`, `O`, `1` and `I` are left out |
| `case_sensitive` | `false` | |
| `session_key` | `flocms_captcha` | Where the code is stored in the session |
| `single_use` | `true` | `verify()` removes the code on every check. Set `false` only if you must check the same code twice, and call `clear()` yourself. |
| `fonts` | three fonts in the package | TrueType font files to pick from |

The code is generated with `random_int()` and compared with `hash_equals()`.

## Challenge lifecycle

`output()` generates and stores a new challenge and renders its image. `refresh()` generates/stores a new code and returns the text; it does not render an image. Don't return that text to the visitor as a refresh endpoint. Reload the image endpoint instead.

One `session_key` stores one code, so refreshing a second form or browser tab using the same key replaces the first form's challenge. Use distinct configured keys for independent forms and the same key for each form's image and verification endpoints.

The package has no challenge timestamp or automatic expiry. `clear()` removes the code, and so does `verify()` with the default `single_use`; session expiry removes session data according to PHP's settings. If a form needs a shorter lifetime, record and check a timestamp in your application as well.

`getCaptcha()` exposes the underlying `Captcha` object: `generateText()` generates without storing, `store($text)` stores, `getStoredText()` reads, and `getConfig()` returns merged configuration. These are server-side utilities; do not expose stored answers to the client.

## Your own renderer

To draw the captcha differently, implement `FloCMS\Captcha\Renderer\RendererInterface` and pass it as the second argument:

```php
use FloCMS\Captcha\Renderer\RendererInterface;

class CustomRenderer implements RendererInterface
{
    public function render(string $text): void
    {
        // send headers and output an image of $text
    }
}

$captcha = new CaptchaManager([], new CustomRenderer());
```

## Captchas in the API

The captcha is stored in the session, and the API has no session by default. Using this CAPTCHA through an API requires deliberate session/cookie integration; it does not become token-based automatically. API rate limits help limit abuse, while idempotency handles safe retries and does not verify that a client is human. See [CORS and rate limiting](/docs/2.0/api-cors#rate-limiting).
