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 запроса.

Чек-лист

  1. Открыть AJAX-запрос в Network.
  2. Проверить, уходит ли CSRF-токен.
  3. Проверить, отправляется ли cookie сессии.
  4. Проверить домен, протокол и поддомен запроса.
  5. Проверить SameSite и Secure у cookies.
  6. Для fetch проверить credentials.
  7. Для другого домена проверить CORS и OPTIONS.
  8. Сравнить формат данных: form-data или JSON.

Отключение CSRF почти никогда не должно быть решением. Правильнее понять, почему браузер не отправил токен или сессию. Тогда форма остаётся защищённой и начинает работать стабильно.

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

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