lenec ru

← все посты

CORS error: No 'Access-Control-Allow-Origin' — реальные причины

10K

Каждый раз, когда я вижу в чате «у меня CORS», я мысленно делю это на три кучи: настоящий CORS, фронт-баг, выдающий себя за CORS, и серверная ошибка, которая случайно похожа на CORS. Различать их умеешь — экономишь часы.

Сообщение в браузере выглядит так:

Access to fetch at 'https://api.example.com/users'
from origin 'https://app.example.com' has been blocked
by CORS policy: No 'Access-Control-Allow-Origin' header is
present on the requested resource.

Браузер заблокировал ответ. Не сервер. Не сеть. Конкретно браузер посмотрел на заголовки и решил, что показывать тебе содержимое нельзя. Дальше нужно понять — почему.

Что такое CORS на пальцах

Браузер не пускает JS со страницы https://app.example.com читать ответы от https://api.example.com, если сервер явно не разрешил. Разрешение — это заголовок Access-Control-Allow-Origin в ответе.

Если запрос «простой» (GET/POST с обычным Content-Type, без кастомных заголовков), браузер делает обычный запрос и проверяет заголовок ответа. Если запрос «сложный» (например, PUT, или с заголовком Authorization) — сначала отправляет preflight: OPTIONS с Access-Control-Request-*. Сервер должен ответить 200/204 и нужным набором Access-Control-Allow-*.

Реальная причина 1: сервер не отдаёт CORS-заголовки

Самое прямое: ты ставишь Express, он по умолчанию ничего про CORS не знает. Запрос с другого Origin приходит — ответа без нужных заголовков. Браузер блокирует.

import express from 'express';
import cors from 'cors';

const app = express();

app.use(cors({
  origin: 'https://app.example.com',
  credentials: true,
}));

app.get('/users', (req, res) => res.json([{ id: 1 }]));

Если фронт сидит на нескольких origin-ах, делаешь массив или функцию:

const allowed = new Set([
  'https://app.example.com',
  'https://staging.example.com',
]);

app.use(cors({
  origin: (origin, cb) => {
    if (!origin || allowed.has(origin)) cb(null, true);
    else cb(new Error('Not allowed by CORS'));
  },
  credentials: true,
}));

Реальная причина 2: preflight уходит в 404 или 401

Это часто. Сервер вроде настроен на CORS, но preflight-запрос (OPTIONS) идёт через middleware с авторизацией, и тот возвращает 401 раньше, чем CORS-заголовки добавляются. Браузер видит ответ без Access-Control-Allow-Origin и говорит, что CORS сломан.

Проверяю в Network в DevTools: смотрю на запрос с методом OPTIONS — что в ответе. Если 401 или 404 — лечить нужно сервер: либо ставить CORS до auth, либо явно пропускать OPTIONS.

app.options('*', cors());
app.use(cors());
app.use(authMiddleware);

Реальная причина 3: куки не идут

Поставил credentials: 'include' на фронте, ждёшь, что куки полетят с запросом. А они не идут, или идут — но сервер их не видит.

Чек-лист:

  • на сервере Access-Control-Allow-Credentials: true;
  • на сервере Access-Control-Allow-Origin — конкретный URL, не *;
  • кука выставлена с SameSite=None; Secure, иначе кросс-сайтом не пойдёт.

Главный момент — Allow-Origin: * с credentials: include не работает. Браузер откажет.

Реальная причина 4: ошибка на бэкенде, а не CORS

Это самый коварный сценарий. На сервере в обработчике падает 500 или 502, ответ уходит без CORS-заголовков (потому что middleware падает раньше). Браузер показывает CORS-сообщение, потому что технически это правда — заголовков нет. А реальная проблема — серверная ошибка.

Симптом — открываешь Network, видишь, что запрос доехал до сервера и там либо 5xx, либо вообще отвалился по таймауту. Реальную причину смотри в логах сервера, а не в DevTools браузера.

Поэтому правило: сначала чекаешь, какой статус-код у запроса, потом думаешь про CORS.

Реальная причина 5: фронт ходит по неправильному URL

Ты в коде указал https://api.example.com, а на сервере существует https://api.example.com/v1. Запрос ловит редирект 301 -> /v1. Редиректы для OPTIONS в большинстве серверов либо не обрабатываются, либо ломают CORS-цикл.

Лечится тривиально — поменять URL на правильный. Но симптом тот же: «у меня CORS». Поэтому смотри полный путь и редиректы в Network.

Что НЕ делать

Не отключать CORS в браузере на проде. На разработческой машине запускать Chrome с --disable-web-security можно для отладки, но это не починка. Это игнорирование симптома.

Не ставить Allow-Origin: * на API с куками. Это либо не сработает, либо откроет API всему миру.

Не пытаться чинить CORS на фронте. CORS — это политика сервера, фронт не может её изменить. Любое лекарство — на бэкенде или в proxy.

Когда нужен прокси

Иногда правильный путь — вообще обойти CORS. Если фронт и API сидят в одном домене (через nginx или Next API routes), CORS не нужен — это same-origin. У меня на нескольких сервисах фронт ходит на /api/..., который проксируется на внутренний бэкенд. Браузер видит запрос на тот же origin, никаких preflight'ов и заголовков.

location /api/ {
    proxy_pass http://127.0.0.1:4000/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

В Next можно сделать то же через rewrites:

module.exports = {
  async rewrites() {
    return [{ source: '/api/:path*', destination: 'https://api.example.com/:path*' }];
  },
};

Алгоритм, по которому я разбираю CORS

  • Открыл DevTools Network. Какой статус у запроса? Если 5xx — это бэк, не CORS.
  • Есть ли preflight (OPTIONS)? Что в его ответе?
  • Какой Origin отправляет браузер и какой Access-Control-Allow-Origin возвращает сервер?
  • Если используются куки — есть ли Allow-Credentials: true и реальный (не *) Origin?
  • Убедился, что URL правильный, без скрытых редиректов.

Девять раз из десяти причина находится в первых двух пунктах. CORS — это не страшно, это просто правила браузера, которые нужно один раз понять.

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

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.