AJAX возвращает 403: как проверить CSRF, cookies и заголовки
AJAX-запрос с кодом 403 часто воспринимают как “сервер не принимает запрос”. На практике причина обычно конкретная: нет CSRF-токена, не отправились cookies, изменился домен, включился SameSite, запрос ушёл с HTTP на HTTPS, CORS заблокировал headers или сессия истекла.
Диагностику лучше начинать не с отключения защиты, а с вкладки Network. Там видно, что реально отправил браузер.
Проверить Network
Откройте DevTools → Network, повторите отправку формы и откройте проблемный запрос. Смотреть нужно:
- Request URL;
- Request Method;
- Status Code;
- Request Headers;
- Form Data или Payload;
- Cookie;
- Response body.
Иногда сервер в ответе прямо пишет: CSRF token validation failed. Но часто это скрыто за общей 403-страницей.
CSRF-токен
Если фреймворк требует CSRF, токен должен уйти вместе с AJAX-запросом. Он может передаваться в POST-поле или заголовке.
<meta name="csrf-token" content="...">
const token = document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content');
fetch('/form/send', {
method: 'POST',
headers: {
'X-CSRF-Token': token,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
Если токен есть в HTML, но не уходит в запросе, проблема во frontend-коде. Если токена нет в HTML, нужно смотреть шаблон страницы или настройки фреймворка.
Сессия и cookies
CSRF часто связан с сессией. Если cookie сессии не отправляется, сервер не может проверить токен.
Проверьте в Network, есть ли Cookie в запросе. Если нет, возможные причины:
- запрос идёт на другой поддомен;
- неверный SameSite;
- cookie поставлена только для HTTPS;
- страница открыта по HTTP;
- fetch не отправляет credentials;
- сессия истекла.
fetch и credentials
Для запросов на другой поддомен или origin может потребоваться credentials.
fetch('https://api.example.ru/form/send', {
method: 'POST',
credentials: 'include',
headers: {
'X-CSRF-Token': token,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
Если запрос идёт на тот же origin, cookies обычно отправляются автоматически, но лучше проверить фактический запрос.
SameSite
После изменений в браузерах SameSite стал частой причиной проблем с cookies. Если форма встроена на другом домене или запрос идёт между поддоменами, cookie может не отправиться.
Set-Cookie: PHPSESSID=...; SameSite=Lax; Secure; HttpOnly
Для cross-site сценариев может потребоваться SameSite=None; Secure, но это нужно делать осознанно, понимая риск.
CORS
Если AJAX идёт на другой домен, одного CSRF мало. Нужны корректные CORS-заголовки. Браузер может сначала отправить OPTIONS preflight.
Access-Control-Allow-Origin: https://example.ru
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, X-CSRF-Token
Нельзя бездумно ставить Access-Control-Allow-Origin: * вместе с credentials. Браузер такой вариант не примет.
JSON вместо form-data
Если сервер ждёт обычный POST, а frontend отправляет JSON, данные могут не попасть туда, куда ожидает backend. Иногда это приводит не к 400, а к 403 из-за отсутствия CSRF-поля.
headers: {
'Content-Type': 'application/json'
}
Проверьте, как backend читает данные: из $_POST, raw body или request parser.
После деплоя
403 может появиться после смены домена, перехода на HTTPS, включения CDN, изменения cookie domain или обновления frontend-сборки. Поэтому нужно сравнить старый и новый URL запроса.
Чек-лист
- Открыть AJAX-запрос в Network.
- Проверить, уходит ли CSRF-токен.
- Проверить, отправляется ли cookie сессии.
- Проверить домен, протокол и поддомен запроса.
- Проверить SameSite и Secure у cookies.
- Для fetch проверить credentials.
- Для другого домена проверить CORS и OPTIONS.
- Сравнить формат данных: form-data или JSON.
Отключение CSRF почти никогда не должно быть решением. Правильнее понять, почему браузер не отправил токен или сессию. Тогда форма остаётся защищённой и начинает работать стабильно.
Комментарии (0)
Пока нет комментариев. Будьте первым!