Integrate reCAPTCHA using async HTTP/3, making your app fast with a few lines.
use Illuminate\Support\Facades\Route;
Route::post('login', function () {
// ...
})->middleware('recaptcha:checkbox');
Your support allows me to keep this package free, up-to-date and maintainable. Alternatively, you can spread the word!
- Laravel 10 or later
You can install the package via Composer:
composer require laragear/recaptcha
Add the reCAPTCHA keys for your site to the environment file of your project. You can add each of them for reCAPTCHA v2 checkbox, invisible, Android, and score.
If you don't have one, generate it in your reCAPTCHA Admin panel.
RECAPTCHA_CHECKBOX_SECRET=6t5geA1UAAAAAN...
RECAPTCHA_CHECKBOX_KEY=6t5geA1UAAAAAN...
RECAPTCHA_INVISIBLE_SECRET=6t5geA2UAAAAAN...
RECAPTCHA_INVISIBLE_KEY=6t5geA2UAAAAAN...
RECAPTCHA_ANDROID_SECRET=6t5geA3UAAAAAN...
RECAPTCHA_ANDROID_KEY=6t5geA3UAAAAAN...
RECAPTCHA_SCORE_SECRET=6t5geA4UAAAAAN...
RECAPTCHA_SCORE_KEY=6t5geA4UAAAAAN...
This allows you to check different reCAPTCHA mechanisms using the same application, in different environments.
Note
ReCaptcha already comes with v2 keys for local development. For v3, you will need to create your own set of credentials once on production.
Usage differs based on if you're using checkbox, invisible, Android challenges, or the v3 score-driven challenge.
After you integrate reCAPTCHA into your frontend or Android app, set the ReCaptcha middleware in the POST
routes where a form with reCAPTCHA is submitted. The middleware will catch the g-recaptcha-response
input (you can change it later) and check if it's valid.
To declare the middleware just use the ReCaptcha
middleware builder:
ReCaptcha::checkbox()
for explicitly rendered checkbox challenges.ReCaptcha::invisible()
for invisible challenges.ReCaptcha::android()
for Android app challenges.
use App\Http\Controllers\ContactController;
use Laragear\ReCaptcha\Http\Middleware\Builders\ReCaptcha;
use Illuminate\Support\Facades\Route;
Route::post('contact', [ContactController::class, 'send'])
->middleware(ReCaptcha::invisible()->forGuests('web')->remember())
If for some reason the challenge is not a success, the validation will immediately kick in and throw a ValidationException
, returning the user back to the form.
To avoid asking for challenges over and over again, you can "remember" the challenge for a given set of minutes. This can be enabled globally, but you may prefer to do it in a per-route basis.
Simple use the remember()
method. You can set the number of minutes to override the global parameter. Alternatively, rememberForever()
will remember the challenge forever.
use App\Http\Controllers\Auth\LoginController;
use Laragear\ReCaptcha\Http\Middleware\Builders\ReCaptcha;
use Illuminate\Support\Facades\Route
Route::post('login', [LoginController::class, 'login'])
->middleware(ReCaptcha::invisible()->remember());
Route::post('message', [ChatController::class, 'login'])
->middleware(ReCaptcha::checkbox()->rememberForever());
You should use this in conjunction with the @robot
directive in your Blade templates to render a challenge when the user has not successfully done one before.
@robot
<div class="g-recaptcha"
data-sitekey="{{ recaptcha('invisible') }}"
data-callback="onSubmit"
data-size="invisible">
</div>
@endrobot
Tip
Good places to remember a challenge for some minutes are forms which are expected to fail, or when you have multiple forms the user may jump between.
You can change the input name from g-recaptcha-response
, which is the default, to anything using input()
.
use App\Http\Controllers\Auth\LoginController;
use Laragear\ReCaptcha\Http\Middleware\Builders\ReCaptcha;
use Illuminate\Support\Facades\Route
Route::post('login', [LoginController::class, 'login'])
->middleware(ReCaptcha::checkbox()->input('recaptcha_input'));
The reCAPTCHA v3 middleware works differently from v2. This response is always a success, but the challenge scores between 0.0
and 1.0
. Human-like interaction will be higher, while robots will score lower. The default threshold is 0.5
, but this can be changed globally or per-route.
To start using it, simply use the ReCaptcha::score()
method to your route.
use App\Http\Controllers\CommentController;
use Laragear\ReCaptcha\Http\Middleware\Builders\ReCaptcha;
use Illuminate\Support\Facades\Route
Route::post('comment', [CommentController::class, 'create'])
->middleware(ReCaptcha::score());
Once the challenge has been received in your controller, you will have access to two methods from the Request class or instance: isHuman()
and isRobot()
, which may return true
or false
:
use App\Models\Post;
use Illuminate\Http\Request;
public function store(Request $request, Post $post)
{
$request->validate([
'body' => 'required|string|max:255'
]);
$comment = $post->comment()->make($request->only('body'));
// Flag the comment as "moderated" if it was a written by robot.
if ($request->isRobot()) {
$comment->markAsModerated();
}
$comment->save();
return view('post.comment.show', ['comment' => $comment]);
}
You can also have access to the response from reCAPTCHA using the response()
method of the ReCaptcha
facade:
use Laragear\ReCaptcha\Facades\ReCaptcha;
$response = ReCaptcha::response();
if ($response->score > 0.2) {
return 'Try again!';
}
Warning
Be careful of calling response()
, as it will throw an exception on controllers without challenges.
The middleware accepts three additional parameters using the middleware helper.
threshold()
: The value that must be above or equal to be considered human.action()
: The action name to optionally check against.input()
: The name of the reCAPTCHA input to verify.
use App\Http\Controllers\CommentController;
use Laragear\ReCaptcha\Http\Middleware\Builders\ReCaptcha;
use Illuminate\Support\Facades\Route
Route::post('comment', [CommentController::class, 'store'])
->middleware(ReCaptcha::score()->threshold(0.7)->action('post-comment')->input('my_score_input');
Important
When checking the action name, ensure your frontend action matches with the expected in the middleware.
Sometimes you may want to bypass reCAPTCHA checks when there is an authenticated user, or automatically receive it as a "human" on score-driven challenges, specially on recurrent actions or when the user already has completed a challenge (like on logins).
To exclude authenticated user you can use forGuests()
, and specify the guards if necessary.
use App\Http\Controllers\CommentController;
use App\Http\Controllers\MessageController;
use DarkGhostHunter\Captcha\ReCaptcha;
use Illuminate\Support\Facades\Route
// Don't challenge users authenticated on the default (web) guard.
Route::post('message/send', [MessageController::class, 'send'])
->middleware(ReCaptcha::invisible()->forGuests());
// Don't challenge users authenticated on the "admin" and "moderator" guards.
Route::post('comment/store', [CommentController::class, 'store'])
->middleware(ReCaptcha::score(0.7)->action('comment.store')->forGuests('admin', 'moderator'));
Then, in your blade files, you can easily skip the challenge with the @guest
or @auth
directives.
<form id="comment" method="post">
<textarea name="body"></textarea>
@auth
<button type="submit">Post comment</button>
@else
<button class="g-recaptcha" data-sitekey="{{ captchavel('invisible') }}" data-callback="onSubmit">
Post comment
</button>
@endauth
</form>
You can easily fake a reCAPTCHA response score in your local development by setting RECAPTCHA_FAKE
to true
.
RECAPTCHA_FAKE=true
This environment variable allows to fake a robot responses by filling the is_robot
input in your form.
<form id="comment" method="post">
<textarea name="body"></textarea>
@env('local')
<input type="checkbox" name="is_robot" checked>
@endenv
<button class="g-recaptcha" data-sitekey="{{ captchavel('invisible') }}" data-callback='onSubmit'>
Post comment
</button>
</form>
ReCaptcha comes with a handy middleware to confirm with a simple reCAPTCHA Checkbox challenge, much like the password confirmation middleware included in Laravel.
First, set the route to protect using the recaptcha.confirm
middleware.
use Illuminate\Support\Facades\Route;
Route::get('/settings', function () {
// ...
})->middleware('recaptcha.confirm');
Route::post('/settings', function () {
// ...
})->middleware('recaptcha.confirm');
Once done, ensure you have also a recaptcha.confirm
route to receive the redirected user, and one to receive the challenge at the same path (as the view POST
request does). ReCaptcha comes with a controller and a basic view that you can use out of the box:
use Illuminate\Support\Facades\Route;
use Laragear\ReCaptcha\Http\Controllers\ConfirmationController;
Route::get('recaptcha', [ConfirmationController::class, 'show'])
->name('recaptcha.confirm');
Route::post('recaptcha', [ConfirmationController::class, 'confirm'])
When the user tries to enter the route, it will be redirected to the view asking to resolve a reCAPTCHA challenge. Once done, it will be redirected to the intended URL.
The middleware it's compatible with remembering challenges, and will use the default amount of time to not ask again if remembering only when it's enabled globally, otherwise it will be asked to confirm every time.
You can configure the route name and the guards to bypass the confirmation if the user is authenticated after the first argument.
use Illuminate\Support\Facades\Route;
Route::get('/settings', function () {
// ...
})->middleware('recaptcha.confirm:my-custom-route,web,admin');
Check the official reCAPTCHA documentation to integrate the reCAPTCHA script in your frontend, or inside your Android application.
You can use the recaptcha()
helper to output the site key depending on the challenge version you want to render: checkbox
, invisible
, android
or score
(v3).
<form id='login' method="POST">
<input type="email" name="email">
<input type="password" name="password">
<button class="g-recaptcha" data-sitekey="{{ recaptcha('invisible') }}" data-callback='onSubmit'>
Login
</button>
</form>
ReCaptcha is intended to work out-of-the-box, but you can publish the configuration file for fine-tuning the reCAPTCHA verification.
php artisan vendor:publish --provider="Laragear\ReCaptcha\ReCaptchaServiceProvider" --tag="config"
You will get a config file with this array:
<?php
return [
'enable' => env('RECAPTCHA_ENABLE', false),
'fake' => env('RECAPTCHA_FAKE', false),
'hostname' => env('RECAPTCHA_HOSTNAME'),
'apk_package_name' => env('RECAPTCHA_APK_PACKAGE_NAME'),
'threshold' => 0.5,
'remember' => [
'enabled' => false,
'key' => '_recaptcha',
'minutes' => 10,
],
'client' => [
'version' => 2.0,
],
'credentials' => [
// ...
]
];
return [
'enable' => env('RECAPTCHA_ENABLE', false),
];
By default, ReCaptcha is disabled, so it doesn't check reCAPTCHA challenges, and on score-driven routes it will always resolve them as human interaction.
You can enable it with the RECAPTCHA_ENABLE
environment variable.
RECAPTCHA_ENABLE=true
This can be handy to enable on some local or development environments to check real interaction using the included localhost test keys, which only work on localhost
.
Important
When switched off, the reCAPTCHA v2 challenges are not validated in the Request input, so you can safely disregard any frontend script or reCAPTCHA tokens or boxes.
RECAPTCHA_FAKE=true
If ReCaptcha is enabled, setting this to true will allow your application to fake v3-score responses from reCAPTCHA servers. For v2 challenges, setting this to true
bypasses the challenge verification.
You should enable it for running unit tests.
Warning
Remember to disable faking on production. Not doing so will fake all score challenges as human, not requiring the challenge token.
RECAPTCHA_HOSTNAME=myapp.com
RECAPTCHA_APK_PACKAGE_NAME=my.package.name
If you are not verifying the Hostname or APK Package Name in your reCAPTCHA Admin Panel, may be because you use multiple hostnames or apps, you will have to issue them in the environment file.
When the reCAPTCHA response from the servers is retrieved, it will be checked against these values when present. In case of mismatch, a validation exception will be thrown.
return [
'threshold' => 0.4
];
The default threshold to check against reCAPTCHA v3 challenges. Values equal or above will be considered "human".
If you're not using reCAPTCHA v3, or you're fine with the default, leave this alone. You can still override the default in a per-route basis.
return [
'remember' => [
'enabled' => false,
'key' => '_recaptcha',
'minutes' => 10,
],
];
Remembering the user once a V2 challenge is successful is disabled by default.
It's recommended to use a per-route basis "remember" if you expect only some routes to remember challenges, instead of the whole application.
This also control how many minutes to set the "remember". You can set INF
constant to remember the challenge forever (or until the session expires).
return [
'client' => [
'version' => 3.0,
],
];
This array sets the options for the outgoing request to reCAPTCHA servers. This is handled by Guzzle, which in turn will pass it to the underlying transport. Depending on your system, it will probably be cURL.
By default, it instructs Guzzle to use HTTP/3 whenever possible.
return [
'credentials' => [
// ...
]
];
Here is the full array of reCAPTCHA credentials to use depending on the version. Do not change the array unless you know what you're doing.
On local development, let the default testing keys be used. These are meant to be used on local development, so in production you can easily change them for real keys.
On unit testing, the middleware will detect the environment and skip the mandatory challenge check. There is no need to disable ReCaptcha.
On local development and unit testing, the middleware and will automatically create human responses. There is no need to disable ReCaptcha, but enabling faking is mandatory to enable faking robot responses using is_robot
on live requests.
Inside your tests, you can fake a response made by a human or robot by simply using the fakeHuman()
and fakeRobot()
methods, which will score 1.0
or 0.0
respectively for all subsequent requests.
<?php
use Laragear\ReCaptcha\Facades\ReCaptcha;
// Let the user login normally.
ReCaptcha::fakeHuman();
$this->post('login', [
'email' => 'test@test.com',
'password' => '123456',
])->assertRedirect('user.welcome');
// ... but if it's a robot, force him to use 2FA.
ReCaptcha::fakeRobot();
$this->post('login', [
'email' => 'test@test.com',
'password' => '123456',
])->assertViewIs('login.2fa');
Note
Fake responses don't come with actions, hostnames or APK package names, so these are not validated.
Alternatively, fakeScore()
method will fake responses with any score you set.
<?php
use Laragear\ReCaptcha\Facades\ReCaptcha;
// A human comment should be public.
ReCaptcha::fakeScore(0.8);
$this->post('comment', [
'body' => 'This comment was made by a human',
])->assertSee('Your comment has been posted!');
// Moderate a post if there is no clear separation.
ReCaptcha::fakeScore(0.4);
$this->post('comment', [
'body' => 'Comment made by something.',
])->assertSee('Your comment will be reviewed before publishing.');
// A robot shouldn't be able to comment.
ReCaptcha::fakeScore(0.2);
$this->post('comment', [
'body' => 'Comment made by robot.',
])->assertSee('Robots are not welcomed here! Go away!');
For users of PhpStorm, there is a stub file and a meta file to aid in macro autocompletion for this package. You can publish them using the phpstorm
tag:
php artisan vendor:publish --provider="Laragear\ReCaptcha\ReCaptchaServiceProvider" --tag="phpstorm"
The file gets published into the .stubs
folder of your project, while the meta is located inside .phpstorm.meta.php
directory. You should point your PhpStorm to these stubs.
- The only singleton registered is the
ReCaptcha
class, which uses a stale config instance. You shouldn't change the config. - There are no static properties written during a request.
There should be no problems using this package with Laravel Octane as intended.
To use HTTP/3, ensure you're using PHP 8.2 or later. cURL version 7.66 supports HTTP/3, and latest PHP 8.2 uses version 7.85.
For more information about checking if your platform can make HTTP/3 requests, check this PHP Watch article.
If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker.
This specific package version is licensed under the terms of the MIT License, at time of publishing.
Laravel is a Trademark of Taylor Otwell. Copyright © 2011-2024 Laravel LLC.