Laravel: CSRF token mismatch - форма не отправляется. Причины и решения

mr. Cooper 2 часа назад Веб-разработка
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 глобально для пользовательских форм. Это защита, которая реально работает.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи