> For the complete documentation index, see [llms.txt](https://docs.johncms.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.johncms.com/10.0/obshie-svedeniya/zashita-ot-csrf.md).

# Защита от CSRF

CSRF (Cross-Site Request Forgery) — атака, при которой чужой сайт заставляет браузер посетителя отправить запрос к вашему сайту от его имени. Браузер приложит к такому запросу куки сессии, и для сайта он будет выглядеть как действие авторизованного пользователя: удаление сообщения, смена пароля, отправка формы.

Защита строится на токене, который знает только ваша страница: форма отправляет его вместе с данными, а сайт сверяет со значением из сессии. Чужой сайт токен получить не может, поэтому его запрос отклоняется.

## Как это работает в JohnCMS

Проверку выполняет middleware, встроенный в конвейер обработки запроса. Он срабатывает раньше, чем запрос доходит до контроллера, и касается всех запросов небезопасными методами: `POST`, `PUT`, `PATCH`, `DELETE`. Запросы `GET` и `HEAD` не проверяются — они не должны менять состояние сайта.

{% hint style="info" %}
До версии 10.0 токен проверялся правилом валидации `Csrf`, которое каждый контроллер подключал сам. Теперь проверка общая, а правило удалено: писать её в контроллере не нужно и не следует.
{% endhint %}

Токен принимается двумя способами:

* поле формы `csrf_token`;
* заголовок `X-CSRF-Token` — для запросов, у которых нет формы (AJAX с телом в JSON, загрузка файлов из редактора).

## Токен в форме

В каждую форму, которая отправляется методом POST, добавьте скрытое поле:

```twig
<form action="{{ url }}" method="post">
    <input type="hidden" name="csrf_token" value="{{ app.csrf_token }}">
    ...
</form>
```

Значение `app.csrf_token` доступно в любом шаблоне сайта. Если формы нет — например, действие выполняется ссылкой, — сделайте маленькую форму с кнопкой: запрос, меняющий данные, не должен выполняться по `GET`.

## Токен в запросах JavaScript

Макеты темы печатают токен в мета-теге:

```html
<meta name="csrf-token" content="…">
```

В стандартной теме axios настроен так, что подставляет заголовок во все запросы автоматически — писать ничего не нужно. Если вы отправляете запросы иначе, возьмите токен из мета-тега сами:

```javascript
const token = document.querySelector('meta[name="csrf-token"]')?.content ?? '';

fetch('/my-module/action', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': token,
    },
    body: JSON.stringify({ id: 42 }),
});
```

Отдельный случай — загрузка файлов редактором CKEditor: он отправляет собственный запрос, поэтому токен передаётся ему в настройках:

```javascript
simpleUpload: {
    uploadUrl: uploadUrl,
    headers: { 'X-CSRF-Token': token },
}
```

## Что происходит при неверном токене

Запрос не доходит до контроллера и получает ответ `403`. Форма ответа зависит от того, кто спрашивает:

* обычный запрос из браузера — страница ошибки с текстом о том, что сессия устарела и страницу нужно обновить;
* AJAX-запрос (`X-Requested-With: XMLHttpRequest`) или запрос с `Accept: application/json` — ответ JSON вида `{"message": "…"}`, который скрипт может показать пользователю.

Каждый отклонённый запрос записывается в лог с URL, методом и заголовком `Referer` — по логу удобно искать формы, в которых забыли токен.

## Исключения

Иногда проверку нужно отключить: например, эндпоинт принимает запросы от внешнего сервиса, у которого токена нет и быть не может.

### На маршруте

Основной способ. Исключение объявляется рядом с маршрутом, поэтому его видно при чтении конфигурации модуля:

```php
$router->post('/my-module/webhook', WebhookController::class)
    ->name('my-module.webhook')
    ->withoutCsrf();
```

Метод есть и у группы маршрутов — тогда он действует на все маршруты внутри, включая вложенные группы:

```php
$router->group('/api', static function (RouteCollection $group): void {
    $group->post('/items', CreateItemController::class);
})->withoutCsrf();
```

### В конфигурации

Для точек входа, маршруты которых вы не редактируете, есть список путей в `config/csrf.php`:

```php
return [
    'except' => [
        '/legacy/endpoint',
        '/api/*',
    ],
];
```

Пути сравниваются с нормализованным путём запроса (без завершающего слэша), допускаются маски в стиле оболочки.

{% hint style="warning" %}
Каждое исключение — это снятая защита. Прежде чем добавлять его, убедитесь, что запрос защищён иначе: подписью, ключом доступа, ограничением по адресу. Для запросов из своего же интерфейса исключение не нужно — достаточно передать заголовок.
{% endhint %}

## Сессия и анонимные посетители

Токен создаётся вместе с сессией, поэтому страница с формой открывает сессию даже анонимному посетителю. Это учитывайте при настройке кеширования на стороне сервера.
