justin991q / cloudflare-turnstile
Cloudflare Turnstile captcha module for Laravel
Package info
gitlab.com/Justin991q/laravel-cloudflare-turnstile
pkg:composer/justin991q/cloudflare-turnstile
Requires
- php: ^8.3
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- illuminate/view: ^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Laravel module for Cloudflare Turnstile, the privacy-friendly CAPTCHA alternative. It provides a Blade directive for the script, a widget view and a validation rule.
Requirements
- PHP 8.3+
- Laravel 11, 12 or 13
Installation
The package isn't published on Packagist, add one of the following repositories to your composer.json first.
GitLab package registry
Tagged releases are published to the GitLab Composer package registry, which is accessed at the group level:
"repositories": [
{
"type": "composer",
"url": "https://gitlab.com/api/v4/group/<group_id>/-/packages/composer/packages.json"
}
]
The registry requires authentication, use a personal access token (read_api scope) or a deploy token (read_package_registry scope):
composer config gitlab-token.gitlab.com <token>
Git repository
"repositories": [
{
"type": "vcs",
"url": "https://gitlab.com/Justin991q/laravel-cloudflare-turnstile.git"
}
]
Then require it:
composer require justin991q/cloudflare-turnstile
The service provider is registered automatically via package discovery.
Configuration
Add your Turnstile keys (from the Cloudflare dashboard) to your .env file:
CLOUDFLARE_TURNSTILE_SITE=your-site-key
CLOUDFLARE_TURNSTILE_SECRET=your-secret-key
| Variable | Required | Default | Description |
|---|---|---|---|
CLOUDFLARE_TURNSTILE_SITE | Yes | — | Site key, rendered in the widget |
CLOUDFLARE_TURNSTILE_SECRET | Yes | — | Secret key, used to verify tokens server-side |
CLOUDFLARE_TURNSTILE_VERIFY_URL | No | https://challenges.cloudflare.com/turnstile/v0/siteverify | Siteverify endpoint |
CLOUDFLARE_TURNSTILE_SCRIPT_URL | No | https://challenges.cloudflare.com/turnstile/v0/api.js | Client script URL |
CLOUDFLARE_TURNSTILE_TIMEOUT | No | 5 | Verification request timeout in seconds |
To customise the config file or the widget view, publish them:
php artisan vendor:publish --tag=turnstile-config
php artisan vendor:publish --tag=turnstile-views
Usage
Script
Load the Turnstile script with the @turnstile directive, typically in your layout's <head>. The argument is used as the script's CSP nonce:
@turnstile({{ app('csp-nonce') }})
Renders:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" defer nonce="..."></script>
Widget
Include the widget inside your form. It uses the configured site key and the application locale:
<form method="POST" action="/register">
@csrf
{{-- ... --}}
@include('turnstile::widget')
</form>
Optionally pass the name of a global JavaScript function, called when the challenge succeeds:
@include('turnstile::widget', ['callback' => 'onTurnstileSuccess'])
The widget adds a cf-turnstile-response field to the form.
Validation
Validate the token with the Turnstile rule:
use Justin991q\CloudflareTurnstile\Rules\Turnstile;
$request->validate([
'cf-turnstile-response' => ['required', new Turnstile()],
]);
The rule sends the token and the client IP to Cloudflare's siteverify endpoint and fails with "Captcha is invalid. Please try again." (translatable) when the token is rejected, the secret key is missing or Cloudflare is unreachable.
Verification is only enforced in production. In any other environment (
APP_ENVother thanproduction) the rule always passes, so local development and tests don't need real keys.
Verifying manually
The TurnstileVerifier is registered as a singleton and can be used directly:
use Justin991q\CloudflareTurnstile\TurnstileVerifier;
$valid = app(TurnstileVerifier::class)->verify($token, $request->ip());
Unlike the rule, the verifier always contacts Cloudflare, regardless of the environment.
Testing
composer install
composer test
The GitLab CI pipeline runs the test suite on PHP 8.3, 8.4 and 8.5 against Laravel 11 (PHP 8.3 and 8.4 only), 12 and 13.
Releasing
Push a semver tag, after the tests pass the pipeline publishes it to the GitLab package registry:
git tag v1.0.0
git push origin v1.0.0
License
The MIT License (MIT). See LICENSE for details.