Капча в форме на Laravel: что нужно знать до подключения
Разбираем, что подготовить до интеграции reCAPTCHA или hCaptcha в форму Laravel: ключи, middleware, локальная разработка и куда падает валидация. В конце — рабочий пример на Laravel 11.

К концу этого материала у вас будет форма Laravel, которая отклоняет отправки без пройденной капчи и показывает пользователю понятную ошибку. На чистую интеграцию reCAPTCHA v2 или hCaptcha уходит примерно 30–40 минут, если ключи уже получены. Основное время съедает не код, а мелочи: домены в настройках капчи, поведение на localhost и правильное место для проверки токена. Про них и поговорим до того, как вы начнёте.
Материал написан под Laravel 11.x и PHP 8.2+. Синтаксис валидации и middleware в Laravel 10 и 11 совпадает, но структура файлов в 11 изменилась (нет app/Http/Kernel.php, регистрация middleware переехала в bootstrap/app.php). Если у вас 10.x, регистрация правил и rule-объектов идентична, отличается только место подключения middleware.
Что подготовить до начала
Половина проблем с капчей возникает не в коде, а на этапе подготовки. Соберите это заранее:
- Site key и secret key — два разных ключа. Site key вставляется в HTML и виден всем, secret key участвует в серверной проверке и хранится в
.env. Для Google reCAPTCHA их выдают в консоли reCAPTCHA, для hCaptcha — в панели дашборда. - Список доменов. При регистрации ключей капча просит указать домены, на которых она будет работать. Если забыть добавить
localhostили127.0.0.1, виджет на локальной машине либо не отрисуется, либо будет отдавать ошибку домена. - Тип капчи. reCAPTCHA v2 ("я не робот" с чекбоксом), reCAPTCHA v3 (невидимая, отдаёт score от 0 до 1) и hCaptcha ведут себя по-разному. v3 не показывает пользователю ничего и требует, чтобы вы сами решали, при каком score блокировать. Определитесь до кода — от этого зависит логика валидации.
- Возможность делать исходящие HTTP-запросы с сервера. Серверная проверка токена — это запрос к
https://www.google.com/recaptcha/api/siteverify(или к endpoint hCaptcha). Если прод за жёстким файрволом без исходящего HTTPS, проверка молча провалится по таймауту.
Капча проверяется дважды: браузер показывает виджет и получает токен, а сервер обязан этот токен подтвердить запросом к API капчи. Проверка только на фронтенде не защищает ни от чего — токен подделывается или обходится напрямую через ваш endpoint.
Где живёт проверка токена
Токен из формы (g-recaptcha-response для reCAPTCHA, h-captcha-response для hCaptcha) прилетает в обычном POST-запросе. Проверять его можно двумя способами.
- Правило валидации (custom rule). Плюс — проверка стоит рядом с остальными правилами формы, ошибка попадает в общий
$errors. Подходит, когда капча привязана к конкретной форме. - Middleware. Плюс — вешается на группу маршрутов, не надо трогать каждый контроллер. Подходит, когда капча на многих формах с одинаковой логикой.
Для одной формы обратной связи или регистрации проще правило. Ниже — рабочий пример именно с ним.
Пример: reCAPTCHA v2 через custom rule
Ключи в .env:
RECAPTCHA_SITE_KEY=YOUR_SITE_KEY
RECAPTCHA_SECRET_KEY=YOUR_SECRET_KEY
В config/services.php добавьте блок, чтобы не читать env напрямую (прямой вызов env() вне конфигов ломается при кешировании конфига через php artisan config:cache):
// config/services.php
'recaptcha' => [
'site_key' => env('RECAPTCHA_SITE_KEY'),
'secret_key' => env('RECAPTCHA_SECRET_KEY'),
],
Правило валидации. Создать заготовку: php artisan make:rule Recaptcha. В Laravel 11 сгенерированный класс реализует интерфейс ValidationRule:
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;
class Recaptcha implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// Пустой токен — пользователь не прошёл виджет
if (empty($value)) {
$fail('Подтвердите, что вы не робот.');
return;
}
// Серверная проверка токена у Google
$response = Http::asForm()->post(
'https://www.google.com/recaptcha/api/siteverify',
[
'secret' => config('services.recaptcha.secret_key'),
'response' => $value,
'remoteip' => request()->ip(),
]
);
// Сетевой сбой или таймаут не должны пропускать форму
if ($response->failed() || $response->json('success') !== true) {
$fail('Проверка капчи не пройдена. Попробуйте ещё раз.');
}
}
}
В форм-реквесте или контроллере правило подключается по имени поля, которое присылает reCAPTCHA:
use App\Rules\Recaptcha;
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email'],
// g-recaptcha-response — имя скрытого поля из виджета
'g-recaptcha-response' => ['required', new Recaptcha()],
]);
В Blade-шаблоне подключается скрипт и виджет. data-sitekey берётся из конфига:
<form method="POST" action="/contact">
@csrf
<input name="name" type="text">
<input name="email" type="email">
<div class="g-recaptcha"
data-sitekey="{{ config('services.recaptcha.site_key') }}"></div>
@error('g-recaptcha-response')
<span>{{ $message }}</span>
@enderror
<button type="submit">Отправить</button>
</form>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
Проверка результата: отправьте форму без клика по чекбоксу — должна появиться ошибка «Подтвердите, что вы не робот». Пройдите чекбокс и отправьте — форма проходит валидацию. Если хотите убедиться, что серверная проверка реально работает, временно подмените secret key на неверный: даже с пройденным чекбоксом форма должна отклоняться.
Чем отличается hCaptcha и reCAPTCHA v3
Код почти тот же, меняются адреса и имена полей. Ниже — что переписать под каждый вариант.
| Параметр | reCAPTCHA v2 | reCAPTCHA v3 | hCaptcha |
|---|---|---|---|
| Поле с токеном | g-recaptcha-response | g-recaptcha-response | h-captcha-response |
| Endpoint проверки | google.com/recaptcha/api/siteverify | тот же | hcaptcha.com/siteverify |
| Виден пользователю | Да, чекбокс | Нет | Да, чекбокс |
| Доп. поле в ответе | — | score (0–1) | — |
Для v3 в правиле недостаточно проверить success: ответ содержит score, и вы сами задаёте порог. Обычно блокируют при score ниже 0.5, но конкретное значение подбирается по вашему трафику — начните с логирования score без блокировки и посмотрите на реальное распределение.
Подводные камни
- Кеш конфига. Если читаете ключи через
env()напрямую в правиле, а на проде выполненconfig:cache,env()вернётnull, и проверка молча сломается. Всегда черезconfig(). - Таймаут HTTP-клиента. По умолчанию у клиента Laravel таймаут задан, но при недоступности API капчи форма зависнет на несколько секунд, а затем отклонится. Задайте явный таймаут через
Http::timeout(5), чтобы поведение было предсказуемым. - CSRF и SPA. Если форма отправляется через fetch/axios без перезагрузки, токен капчи нужно вытащить из виджета вручную (
grecaptcha.getResponse()) и положить в тело запроса — из DOM он сам в JSON не попадёт. - Локальная разработка. reCAPTCHA работает на
localhost, только если домен добавлен в настройках ключа. Для тестов Google даёт публичные тестовые ключи, которые всегда возвращают success — но их нельзя использовать на проде.
Что делать дальше
После того как форма отклоняет отправки без капчи, займитесь тремя вещами. Первое — логируйте отказы капчи, чтобы видеть, не блокируете ли живых пользователей (частая жалоба на reCAPTCHA v3 с высоким порогом). Второе — добавьте rate limiting на сам маршрут через throttle middleware: капча не заменяет ограничение частоты запросов. Третье — если переходите на v3, начните с режима наблюдения: пишите score в лог, не блокируя, и подберите порог по своим данным. Актуальные имена полей и формат ответа сверяйте в документации выбранной капчи — они периодически уточняются.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: Laravel 11 — Validation (Custom Rules), Google reCAPTCHA — Verifying the user's response
Частые вопросы
А если капча не работает на localhost?
У меня Laravel 10, а не 11 — код подойдёт?
Виджет отрисовывается, но форма всё равно проходит без капчи. Что не так?
Чем reCAPTCHA v3 отличается на практике от v2?
Нужно ли всё равно ставить rate limiting, если есть капча?
Что если API капчи недоступен в момент отправки?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.