CSRF token mismatch в Yii2: почему форма перестала отправляться

CSRF token mismatch в Yii2 обычно появляется в самый неудобный момент: форму немного поправили, добавили AJAX, включили HTTPS или перенесли сайт на другой домен. Пользователь видит, что форма не отправляется, а в логах появляется ошибка проверки CSRF.

Самое плохое решение — просто выключить CSRF-защиту для всего контроллера. Иногда это делают “временно”, а потом забывают вернуть. Лучше найти конкретную причину, почему токен не доходит или не совпадает.

Как работает CSRF в общих чертах

Yii2 хранит CSRF-токен в cookie и ожидает получить его обратно в POST-запросе. Для обычной формы токен обычно добавляется скрытым полем. Для AJAX его часто передают через заголовок.

<input type="hidden" name="_csrf" value="<?= Yii::$app->request->csrfToken ?>">

Если токена нет, он старый или cookie не совпадает с запросом, Yii2 отклоняет отправку.

Проверить HTML формы

Сначала нужно открыть исходный HTML формы и убедиться, что скрытое поле есть. Если форму переписали вручную, токен могли случайно удалить.

<form action="/contact/send" method="post">
    <input type="hidden" name="_csrf" value="...">
    <input type="text" name="phone">
</form>

При использовании ActiveForm Yii2 обычно добавляет токен сам. Если форму собирают вручную, за токен отвечает разработчик.

AJAX-запросы

При AJAX-отправке нужно проверить, что токен действительно уходит в запросе. Это видно во вкладке Network в браузере: Headers или Payload.

<meta name="csrf-token" content="<?= Yii::$app->request->csrfToken ?>">
$.ajaxSetup({
    headers: {
        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
    }
});

Если форма подгружается динамически, старый обработчик может брать токен до появления meta или hidden-поля. Тогда запрос уходит без токена, хотя в итоговом HTML он есть.

Кэш страницы с формой

Если страница с формой кэшируется целиком, в кэш может попасть старый CSRF-токен. Следующий пользователь получит чужой или устаревший токен, и отправка будет отклонена.

Формы с CSRF не стоит кэшировать как статический HTML без исключений. Если используется fragment cache, форму лучше вынести из кэшируемого фрагмента.

<?php if ($this->beginCache('landing-block', ['duration' => 3600])): ?>
    <p>Кэшируемый текстовый блок</p>
<?php $this->endCache(); endif; ?>

<?= $this->render('_form', ['model' => $model]) ?>

Домен, HTTPS и cookies

CSRF завязан на cookie, поэтому после смены домена, включения HTTPS или перехода с www на non-www могут появиться проблемы. Нужно проверить, что форма отправляется на тот же домен, где открыта страница.

curl -I https://example.ru/contact
curl -I https://www.example.ru/contact

Если страница открыта на www.example.ru, а action формы ведёт на example.ru, cookie может не подойти. Для внутренних форм обычно безопаснее использовать относительный action:

<form action="/contact/send" method="post">

SameSite и secure

При сложных сценариях с поддоменами или внешними виджетами могут мешать настройки SameSite и secure у cookie. Для обычного сайта чаще достаточно стандартных настроек, но после HTTPS стоит проверить параметры cookie.

'request' => [
    'csrfCookie' => [
        'httpOnly' => true,
        'secure' => true,
        'sameSite' => yii\web\Cookie::SAME_SITE_LAX,
    ],
],

secure стоит включать, когда сайт стабильно работает через HTTPS. Если часть пользователей остаётся на HTTP, cookie с secure не будет передаваться.

Не отключать защиту без причины

Иногда для API или webhook действительно отключают CSRF на конкретном action. Но для обычной формы обратной связи это чаще признак, что проблему не разобрали.

public function beforeAction($action)
{
    if ($action->id === 'webhook') {
        $this->enableCsrfValidation = false;
    }

    return parent::beforeAction($action);
}

Отключать проверку для всего контроллера ради одной формы не стоит.

Чек-лист диагностики

  1. Проверить наличие hidden-поля _csrf в HTML.
  2. Проверить Payload и Headers AJAX-запроса.
  3. Проверить, не кэшируется ли форма со старым токеном.
  4. Проверить action формы и домен отправки.
  5. Проверить HTTP/HTTPS и secure cookie.
  6. Проверить www и non-www.
  7. Проверить настройки SameSite, если есть поддомены.
  8. Не отключать CSRF глобально без отдельной причины.

CSRF-ошибка обычно показывает, что форма и cookie перестали быть согласованы. Если идти по цепочке: HTML, AJAX, cookie, домен, кэш, причина находится достаточно быстро.

Комментарии (0)

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