usarise / turnstile-php

PHP library for Turnstile, is Cloudflare’s smart CAPTCHA alternative. It can be embedded into any website without sending traffic through Cloudflare and works without showing visitors a CAPTCHA.
The Unlicense
16 stars 1 forks source link
abuse anti-bot anti-spam captcha cf-turnstile cfturnstile cloudflare cloudflare-turnstile php php-library spam turnstile

Turnstile PHP client library

PHP Version Latest Version License Total Downloads GitHub CI

Inspired on recaptcha

Table of contents

  1. Installation
  2. Getting started
  3. Usage

Installation

composer require usarise/turnstile

Getting started

Installation symfony http client and nyholm psr7 and usarise turnstile

composer require symfony/http-client nyholm/psr7 usarise/turnstile

TurnstileExample.php

<?php

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

use Symfony\Component\HttpClient\Psr18Client;
use Turnstile\Error\Code;
use Turnstile\Turnstile;

// Get real API keys at https://dash.cloudflare.com/?to=/:account/turnstile
$siteKey = '1x00000000000000000000AA'; // Always passes (Dummy Testing)
$secretKey = '1x0000000000000000000000000000000AA'; // Always passes (Dummy Testing)

if ($token = $_POST['cf-turnstile-response'] ?? null) {
    $turnstile = new Turnstile(
        client: new Psr18Client(),
        secretKey: $secretKey,
    );

    $response = $turnstile->verify(
        $token, // The response provided by the Turnstile client-side render on your site.
        $_SERVER['REMOTE_ADDR'], // With usage CloudFlare: $_SERVER['HTTP_CF_CONNECTING_IP']
    );

    if ($response->success) {
        echo 'Success!';
    } else {
        $errors = $response->errorCodes;
        var_dump($errors);
        var_dump(Code::toDescription($errors));
    }

    exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Turnstile example</title>
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form action="" method="POST">
  <!-- The following line controls and configures the Turnstile widget. -->
  <div class="cf-turnstile" data-sitekey="<?php echo $siteKey; ?>" data-theme="light"></div>
  <!-- end. -->
  <button type="submit" value="Submit">Verify</button>
</form>
</body>
</html>

Response to string

var_dump((string) $response);

Response to array

var_dump($response->toArray());

Response object to array

var_dump($response->toArray(strict: true));

Usage Turnstile

Construct

use Turnstile\Client\Client;
use Turnstile\Turnstile;

$turnstile = new Turnstile(
    client: new Client(...),
    secretKey: 'secret key',
    idempotencyKey: 'idempotency key',
);

Simplified construct

PSR-18 Clients like php-http/discovery

$turnstile = new Turnstile(
    client: new Psr18Client(),
    secretKey: 'secret key',
    idempotencyKey: 'idempotency key',
);

Usage Client

Construct

use Turnstile\Client\Client;
use Turnstile\TurnstileInterface;

$client = new Client(
    client: ..., // implementation Psr\Http\Client\ClientInterface
    requestFactory: ..., // implementation Psr\Http\Message\RequestFactoryInterface (default: requestFactory = client)
    streamFactory: ..., // implementation Psr\Http\Message\StreamFactoryInterface (default: streamFactory = requestFactory)
    siteVerifyUrl: TurnstileInterface::SITE_VERIFY_URL, // https://challenges.cloudflare.com/turnstile/v0/siteverify (default)
);

Examples http clients

Guzzle http client

Installation
composer require guzzlehttp/guzzle
Usage
use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Psr7\HttpFactory;
use Turnstile\Client\Client;

$client = new Client(
    new GuzzleHttpClient(),
    new HttpFactory(),
);

Symfony http client and Nyholm PSR-7

Installation symfony http client and nyholm psr7
composer require symfony/http-client nyholm/psr7
Usage
use Symfony\Component\HttpClient\Psr18Client;
use Turnstile\Client\Client;

$client = new Client(
    new Psr18Client(),
);
Simplified construct
use Symfony\Component\HttpClient\Psr18Client;

$client = new Psr18Client();

Symfony http client and Guzzle PSR-7

Installation symfony http client and guzzlehttp psr7
composer require symfony/http-client guzzlehttp/psr7
Usage
use GuzzleHttp\Psr7\HttpFactory;
use Symfony\Component\HttpClient\Psr18Client;
use Turnstile\Client\Client;

$client = new Client(
    new Psr18Client(
        responseFactory: new HttpFactory(),
    ),
);
Simplified construct
use GuzzleHttp\Psr7\HttpFactory;
use Symfony\Component\HttpClient\Psr18Client;

$client = new Psr18Client(
    responseFactory: new HttpFactory(),
);

Symfony http client and Guzzle PSR-7 and Discovery

Installation symfony http client and guzzlehttp psr7 and php http discovery
composer require symfony/http-client guzzlehttp/psr7 php-http/discovery
Usage
use Symfony\Component\HttpClient\Psr18Client;
use Turnstile\Client\Client;

$client = new Client(
    new Psr18Client(),
);
Simplified construct
use Symfony\Component\HttpClient\Psr18Client;

$client = new Psr18Client();

Curl http client and Nyholm PSR-7

Installation nyholm psr7 and php http curl client
composer require nyholm/psr7 php-http/curl-client
Usage
use Http\Client\Curl\Client as CurlClient;
use Nyholm\Psr7\Factory\Psr17Factory;
use Turnstile\Client\Client;

$psr17Factory = new Psr17Factory();

$client = new Client(
    client: new CurlClient(
        responseFactory: $psr17Factory,
        streamFactory: $psr17Factory,
    ),
    requestFactory: $psr17Factory,
);

Discovery http client

Installation php http discovery
composer require php-http/discovery
Usage
use Http\Discovery\Psr18Client;
use Turnstile\Client\Client;

$client = new Client(
    new Psr18Client(),
);
Simplified construct
use Http\Discovery\Psr18Client;

$client = new Psr18Client();

Usage secret key

The widget’s secret key. The secret key can be found under widget settings in the Cloudflare dashboard under Turnstile.

Real keys

API keys at https://dash.cloudflare.com/?to=/:account/turnstile

Test keys

1x0000000000000000000000000000000AA Always passes

2x0000000000000000000000000000000AA Always fails

3x0000000000000000000000000000000AA Yields a “token already spent” error

Example

use Turnstile\Client\Client;
use Turnstile\Turnstile;

// Real API keys at https://dash.cloudflare.com/?to=/:account/turnstile
$secretKey = '1x0000000000000000000000000000000AA';

$turnstile = new Turnstile(
    client: $client,
    secretKey: $secretKey,
);

Usage idempotency key

If an application requires to retry failed requests, it must utilize the idempotency functionality.

You can do so by providing a UUID as the idempotencyKey parameter and then use $turnstile->verify(...) with the same token the required number of times.

Example with Ramsey UUID

Installation
composer require ramsey/uuid
Usage
use Ramsey\Uuid\Uuid;
use Turnstile\Client\Client;
use Turnstile\Turnstile;

$turnstile = new Turnstile(
    client: $client,
    secretKey: $secretKey, // The site’s secret key.
    idempotencyKey: (string) Uuid::uuid4(), // The UUID to be associated with the response.
);

$response = $turnstile->verify(
    $token, // The response that will be associated with the UUID (idempotencyKey)
);

if ($response->success) {
    // ...
}

$response = $turnstile->verify(
    $token, // The response associated with UUID (idempotencyKey)
);

if ($response->success) {
    // ...
}

Usage verify

Sample

$response = $turnstile->verify(
    token: $_POST['cf-turnstile-response'], // The response provided by the Turnstile client-side render on your site.
);

Remote IP

The remoteIp parameter helps to prevent abuse by ensuring the current visitor is the one who received the token.

This is currently not strictly validated.

Basic usage
$response = $turnstile->verify(
    token: $_POST['cf-turnstile-response'], // The response provided by the Turnstile client-side render on your site.
    remoteIp: $_SERVER['REMOTE_ADDR'], // The visitor’s IP address.
);
With usage CloudFlare
$response = $turnstile->verify(
    token: $_POST['cf-turnstile-response'], // The response provided by the Turnstile client-side render on your site.
    remoteIp: $_SERVER['HTTP_CF_CONNECTING_IP'], // The visitor’s IP address.
);

Extended

$response = $turnstile->verify(
    ...
    challengeTimeout: 300, // Number of allowed seconds after the challenge was solved.
    expectedHostname: $_SERVER['SERVER_NAME'], // Expected hostname for which the challenge was served.
    expectedAction: 'login', // Expected customer widget identifier passed to the widget on the client side.
    expectedCdata: 'sessionid-123456789', // Expected customer data passed to the widget on the client side.
);

Usage response

Success status

$response->success

Error codes

$response->errorCodes

Challenge timestamp

$response->challengeTs

Hostname

$response->hostname

Action

$response->action

Customer data

$response->cdata

To string

String with raw json data

(string) $response

To array

Decoded json data

$response->toArray()

Object to array

Array of processed json data based on properties of Response class: success, errorCodes, challengeTs, hostname, action, cdata

$response->toArray(strict: true)

Usage error codes to description

Convert error codes to a description in a suitable language (default english)

use Turnstile\Error\{Code, Description};

var_dump(
    Code::toDescription(
        codes: $response->errorCodes,
        descriptions: Description::TEXTS, // Default
    ),
);