Laravel: CSRF token mismatch - форма не отправляется. Причины и решения
Отправляешь форму в Laravel, а в ответ получаешь ошибку 419 Page Expired или CSRF token mismatch? Это одна из самых частых проблем у новичков и опытных разработчиков. В статье разберём, почему это происходит, как быстро исправить и что сделать, чтобы ошибка не возникала снова.
Что такое CSRF и зачем Laravel его проверяет
CSRF (Cross-Site Request Forgery) - атака, при которой злоумышленник заставляет браузер пользователя отправить запрос на чужой сайт от его имени. Например, пользователь залогинен в интернет-банке - и вредоносная страница незаметно переводит деньги через скрытую форму.
Laravel защищает от этого с помощью токена: при каждой загрузке формы генерируется уникальная строка, которая хранится в сессии. При отправке формы Laravel сравнивает токен в запросе с тем, что сохранён в сессии. Если они не совпадают - запрос отклоняется.
Почему возникает ошибка CSRF token mismatch
Причин несколько, и большинство из них решаются за пять минут:
Забыли добавить @csrf в форму - самая частая причина у новичков.
Сессия истекла - пользователь долго не взаимодействовал со страницей.
Кэширование страниц - форма отдаётся из кэша со старым токеном.
AJAX-запрос без токена - fetch или axios не передают заголовок X-CSRF-TOKEN.
Проблемы с куками - сессионная кука не передаётся (другой домен, HTTPS/HTTP мешанина).
Неправильная конфигурация домена - SESSION_DOMAIN в .env не совпадает с реальным адресом.
Маршрут добавлен в исключения VerifyCsrfToken - токен не генерируется намеренно, но форма его ожидает.
Частые ошибки и симптомы
Симптом | Вероятная причина |
|---|---|
419 Page Expired при отправке формы | Нет @csrf или сессия истекла |
TokenMismatchException в логах | Токен не передаётся или не совпадает |
Форма работает локально, но не на сервере | Проблема с куками или доменом сессии |
AJAX возвращает 419 | Нет заголовка X-CSRF-TOKEN |
После долгого бездействия форма перестаёт работать | Истёк срок жизни сессии |
Пошаговое решение
Шаг 1. Добавьте @csrf в HTML-форму
Это первое, что нужно проверить. Директива Blade @csrf генерирует скрытое поле с токеном.
<form method="POST" action="/your-route">
@csrf
<input type="text" name="name">
<button type="submit">Отправить</button>
</form>Она раскрывается в:
<input type="hidden" name="_token" value="abc123...">Шаг 2. Проверьте AJAX-запросы
Если используете fetch или axios, токен нужно передавать в заголовке.
Вариант с axios (рекомендуется):
// В resources/js/bootstrap.js уже есть этот код - убедитесь, что он подключён
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').content;В шаблоне <head> добавьте мета-тег:
<meta name="csrf-token" content="{{ csrf_token() }}">Вариант с fetch:
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/your-route', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': token
},
body: JSON.stringify({ name: 'value' })
});Шаг 3. Проверьте конфигурацию сессий
Откройте файл .env и убедитесь, что настройки корректны:
SESSION_DRIVER=file # или database, redis
SESSION_LIFETIME=120 # минуты
SESSION_DOMAIN= # оставьте пустым для localhost
SESSION_SECURE_COOKIE=false # true только при HTTPSЕсли сайт работает через HTTPS, а SESSION_SECURE_COOKIE=true - куки не будут передаваться по HTTP, и сессия будет теряться.
Шаг 4. Исключите маршрут из проверки CSRF (если нужно)
Иногда нужно отключить CSRF для отдельных маршрутов - например, для вебхуков от внешних сервисов (Stripe, Telegram и т.д.).
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'webhook/stripe',
'api/*',
];Не добавляйте сюда пользовательские формы - это откроет уязвимость.
Шаг 5. Сбросьте кэш конфигурации
После изменений в .env обязательно выполните:
php artisan config:clear
php artisan cache:clear
php artisan session:clear # если используете драйвер database или redisШаг 6. Проверьте права на папку хранения сессий
Если драйвер сессий - file, Laravel сохраняет их в storage/framework/sessions. Убедитесь, что папка доступна для записи:
chmod -R 775 storage
chown -R www-data:www-data storageПрактические примеры
Пример 1: Форма логина не работает после деплоя
Проблема: после деплоя форма возвращает 419.
Причина: старые сессионные файлы несовместимы с новым APP_KEY.
Решение:
php artisan session:clear
php artisan config:clearПример 2: AJAX-запрос в SPA на Vue/React
Если фронтенд и бэкенд на разных доменах - стандартный CSRF не работает. Используйте Laravel Sanctum:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateВ bootstrap.js настройте:
axios.defaults.withCredentials = true;Перед первым запросом вызовите:
await axios.get('/sanctum/csrf-cookie');Пример 3: Форма работает, но только первый раз
Причина: после отправки токен обновляется, а страница кэшируется с устарелым токеном.
Решение: отключите кэширование для страниц с формами или обновляйте токен через JavaScript:
// После успешного ответа обновляем токен
axios.interceptors.response.use(response => {
const newToken = response.headers['x-csrf-token'];
if (newToken) {
document.querySelector('meta[name="csrf-token"]').content = newToken;
}
return response;
});Часто задаваемые вопросы (FAQ)
Q: Почему форма работает на localhost, но не на продакшн-сервере? A: Скорее всего, проблема в SESSION_SECURE_COOKIE=true при HTTP или в несовпадении SESSION_DOMAIN. Проверьте .env на сервере.
Q: Можно ли полностью отключить CSRF-защиту в Laravel? A: Можно, но крайне нежелательно. Уберите VerifyCsrfToken из $middlewareGroups в app/Http/Kernel.php. Делайте это только для API-маршрутов с другим механизмом аутентификации (например, Sanctum или Passport).
Q: Как передать CSRF-токен в Postman для тестирования? A: Добавьте заголовок X-CSRF-TOKEN со значением токена, или получите его через cookie XSRF-TOKEN и передайте как X-XSRF-TOKEN. Проще - добавить маршрут в $except на время тестирования.
Q: Почему ошибка возникает только у некоторых пользователей? A: Вероятно, у них истекает сессия быстрее (браузер удаляет куки при закрытии, агрессивный антивирус, приватный режим). Увеличьте SESSION_LIFETIME или переключитесь на SESSION_DRIVER=database.
Q: Как проверить, что токен вообще генерируется? A: Откройте DevTools → вкладку Elements и найдите <input type="hidden" name="_token">. Или выполните {{ csrf_token() }} прямо в Blade-шаблоне.
Q: Ошибка 419 возникает при автосохранении через AJAX каждые 30 секунд. Как исправить? A: Перед каждым запросом обновляйте токен через эндпоинт или используйте {{ csrf_token() }} в JavaScript-переменной, которую периодически обновляете через мета-тег.
Q: Форма с enctype="multipart/form-data" не работает - это тоже CSRF? A: Да, директива @csrf нужна и для форм с загрузкой файлов. Убедитесь, что @csrf стоит внутри тега <form>.
Q: Как настроить CSRF для нескольких поддоменов? A: В .env установите SESSION_DOMAIN=.yourdomain.com (с точкой в начале) - тогда сессия будет работать для всех поддоменов.
Полезные советы и лучшие практики
Всегда добавляйте @csrf как первый элемент внутри <form> - чтобы не забыть.
Для API используйте Sanctum или Passport вместо стандартного CSRF-токена.
Логируйте TokenMismatchException - это поможет понять, у каких пользователей и когда возникает проблема.
Не увлекайтесь добавлением маршрутов в $except - каждое исключение - потенциальная уязвимость.
При использовании nginx/Apache с reverse proxy убедитесь, что куки корректно проксируются и не теряются.
Используйте SESSION_DRIVER=redis на нагруженных проектах - файловый драйвер может давать гонки при параллельных запросах.
Проверяйте APP_KEY - если он изменился после деплоя, все старые сессии станут невалидными.
Итог
Ошибка CSRF token mismatch в Laravel в 90% случаев решается одним из трёх способов: добавить @csrf в форму, настроить заголовок для AJAX-запросов или исправить конфигурацию сессий в .env. Если проблема не уходит - проверьте права на папку storage, корректность домена сессии и режим работы HTTPS.
Главное правило: никогда не отключайте CSRF глобально для пользовательских форм. Это защита, которая реально работает.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.