Короткий ответ
Основная защита — CSRF-токен, уникальный для сессии пользователя, который сервер требует в теле или заголовке каждого изменяющего состояние запроса (POST/PUT/DELETE) и сверяет с токеном, привязанным к сессии; дополнительно cookie сессии стоит выставлять с атрибутом SameSite=Lax или Strict.
Как это работает подробнее
Браузер автоматически прикладывает cookie к любому запросу на домен сессии, даже инициированному со стороннего сайта, поэтому один факт наличия валидной cookie не доказывает, что запрос инициирован самим пользователем через легитимный интерфейс; CSRF-токен передаётся не через cookie, а отдельно (скрытое поле формы, заголовок), и сторонний сайт не может его узнать из-за политики same-origin.
Пример кода
request.http
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="a1b2c3d4">
<button type="submit">Transfer</button>
</form>
Set-Cookie: sessionid=xyz; HttpOnly; Secure; SameSite=LaxЧто использовать на практике
- Практика. Для форм на стороне сервера токен обычно встраивается в скрытое поле при рендеринге страницы; для SPA токен можно получать отдельным эндпоинтом и передавать в заголовке; GET-запросы не должны изменять состояние и поэтому не нуждаются в CSRF-защите.
Подводные камни
- Риск. Защищают CSRF-токеном только часть изменяющих эндпоинтов, оставляя «забытые» POST-формы уязвимыми; полагаются только на SameSite cookie, не учитывая, что Lax всё ещё пропускает top-level GET-навигацию; путают CSRF-защиту с CORS, хотя это разные механизмы для разных сценариев атак.