Перейти к содержимому

CORS от А до Я: история, ошибки и грамотная настройка

Опубликовано04.09.2026
Обновлено04.09.2026
Просмотры8
Время чтения16 мин.
Оглавление статьи
Когда браузер блокирует запрос к другому домену, причина не всегда связана с ошибкой в коде приложения: ограничения возникают из-за правил обмена данными между источниками. Чтобы быстро находить проблему, нужно понимать роль заголовков, предварительных запросов, методов и учетных данных. Разберем, почему появляются типичные сообщения в консоли, какие настройки выполняются на сервере, где часто допускают ошибки и как проверять конфигурацию без ослабления защиты приложения.
CORS от А до Я: история, ошибки и грамотная настройка

CORS (Cross-Origin Resource Sharing) — это механизм браузера и HTTP-заголовков, который позволяет серверу сообщить, каким другим origin разрешено читать его ответы из JavaScript. Именно CORS участвует в ситуации, когда фронтенд открыт на https://app.example.com, а fetch() обращается к на https://api.example.com.

Если настройка не совпадает с запросом, в DevTools появляется что-то вроде blocked by CORS policy или No 'Access-Control-Allow-Origin' header. Исправлять это почти всегда нужно на сервере, reverse proxy или API gateway — не отключением защиты в браузере.

Самая важная мысль: CORS не отвечает на вопрос «можно ли вообще отправить HTTP-запрос этому API». Он определяет, разрешит ли браузер JavaScript страницы другого origin получить доступ к ответу. Поэтому Postman или curl могут работать, когда тот же запрос из браузера падает с CORS error.

CORS error: что проверить в первую очередь

Ошибка в консолиЧастая причинаГде исправлять
No 'Access-Control-Allow-Origin' headerСервер не вернул разрешённый originBackend / Nginx / API gateway
... has been blocked by CORS policyОдин из CORS-заголовков не соответствует запросуСначала смотреть Network → OPTIONS/response
preflight request did not succeedOPTIONS не дошёл до приложения или завершился ошибкойProxy, auth middleware, router, backend
Request header field ... is not allowedЗапрошенный header отсутствует в разрешённыхAccess-Control-Allow-Headers
Method ... is not allowedМетод не указан среди разрешённыхAccess-Control-Allow-Methods
Credential is not supported if ACAO is '*'Credentials используются вместе с wildcard originУказать конкретный origin
Mixed ContentHTTPS-страница запрашивает HTTP-ресурсЭто не CORS: перевести API на HTTPS
origin 'null'Запуск из file://, sandboxed iframe или другого opaque originИзменить способ запуска или явно продумать политику

Если нужен быстрый алгоритм: откройте DevTools → Network, найдите запрос, проверьте его Origin, затем ответ сервера и, если есть, отдельный запрос OPTIONS. Это быстрее, чем случайно добавлять заголовки по одному.

Что такое origin

Origin определяется тремя частями URL:

  • scheme — http или https;

  • host — например, example.com;

  • port — например, 443 или 8080.

Путь не входит в origin.

URL относительно https://example.comТот же origin?Почему
https://example.com/apiДаИзменился только path
https://example.com/usersДаPath не влияет на origin
http://example.comНетДругой scheme
https://api.example.comНетДругой host
https://example.com:8443НетДругой port

Поэтому даже frontend и backend одной компании могут быть cross-origin, если находятся на разных поддоменах или портах.

Зачем появилась Same-Origin Policy

Без ограничений вредоносная страница могла бы попытаться использовать браузер пользователя для чтения данных другого сайта, где пользователь уже авторизован.

Same-Origin Policy (SOP) изолирует документы и скрипты разных origins. Концепция появилась ещё в ранних браузерах эпохи Netscape и со временем стала одним из фундаментальных механизмов безопасности веб-платформы.

CORS появился не как замена SOP, а как контролируемый способ ослабить её ограничения для HTTP-доступа: сервер сам сообщает браузеру, какие внешние origins могут читать его ресурсы.

SOP: по умолчанию не доверяй другому origin. CORS: сервер может явно разрешить определённое cross-origin взаимодействие.

Как работает CORS

Представим frontend:

https://app.example.com

и API:

https://api.example.com/users

JavaScript выполняет:

Cross-origin fetch

const response = await fetch('https://api.example.com/users');

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

const users = await response.json();

Поскольку origins различаются, браузер применяет CORS. В запросе он может отправить:

Origin: https://app.example.com

Сервер разрешает frontend ответом:

Access-Control-Allow-Origin: https://app.example.com

После этого браузер позволяет JavaScript получить response.

Подробнее о самом fetch(), Promise и обработке ошибок есть в материале ResizeWeb про асинхронный JavaScript.

Важный нюанс: CORS не обязательно останавливает запрос

Фраза «CORS заблокировал запрос» удобна, но технически часто неточна.

Для некоторых cross-origin запросов браузер действительно отправляет HTTP-запрос серверу. Сервер может его обработать и вернуть ответ. Затем браузер проверяет CORS headers и запрещает JavaScript страницы прочитать ответ.

CORS не является защитой API от запросов. Если операция требует авторизации, защищайте её authentication и authorization на сервере. Клиент без браузерной CORS-проверки — например, другой backend — всё равно способен отправить HTTP-запрос.

Когда preflight не нужен

Не каждый cross-origin запрос вызывает OPTIONS. Fetch Standard определяет набор CORS-safelisted методов и headers, для которых предварительная проверка не требуется.

Упрощённый пример:

fetch('https://api.example.com/public')

Такой GET без нестандартных headers обычно не требует preflight.

Даже POST не автоматически означает preflight. Значение имеют метод, headers и Content-Type.

Поэтому правило «GET и POST — простые, PUT и DELETE — сложные» тоже слишком грубое. Например, POST с Content-Type: application/json обычно приводит к preflight.

Что такое CORS preflight

Preflight — предварительный запрос OPTIONS, с помощью которого браузер выясняет, разрешает ли сервер будущий cross-origin запрос.

Например frontend хочет выполнить:

Запрос, вызывающий preflight

await fetch('https://api.example.com/users/42', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer TOKEN'
  }
});

Перед DELETE браузер может отправить:

OPTIONS /users/42
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization

А сервер должен ответить политикой, допускающей этот запрос:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: DELETE
Access-Control-Allow-Headers: Authorization

Только после успешной проверки браузер отправляет actual request.

Как проверить preflight вручную

Браузер формирует CORS preflight автоматически, но похожий запрос удобно воспроизвести через curl при диагностике сервера.

Проверка CORS preflight через curl

curl -i -X OPTIONS 'https://api.example.com/users/42' \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: DELETE' \
  -H 'Access-Control-Request-Headers: Authorization, Content-Type'

Проверьте, что response содержит нужные Access-Control-Allow-* headers и что OPTIONS не получает 301, 401, 403, 404 или 500 раньше CORS middleware.

Основные CORS-заголовки

HeaderКто отправляетНазначение
OriginБраузерOrigin страницы, инициировавшей запрос
Access-Control-Allow-OriginСерверКакому origin разрешено читать response
Access-Control-Request-MethodБраузер в preflightМетод будущего actual request
Access-Control-Allow-MethodsСерверРазрешённые методы
Access-Control-Request-HeadersБраузер в preflightHeaders будущего запроса
Access-Control-Allow-HeadersСерверРазрешённые request headers
Access-Control-Allow-CredentialsСерверРазрешает credentialed CORS response
Access-Control-Expose-HeadersСерверОткрывает JavaScript дополнительные response headers
Access-Control-Max-AgeСерверПозволяет браузеру кэшировать результат preflight

Access-Control-Allow-Origin: * или конкретный домен?

Для действительно публичного ресурса без credentials возможна политика:

Access-Control-Allow-Origin: *

Для приложения с определённым frontend лучше сузить доступ:

Access-Control-Allow-Origin: https://app.example.com

Если сервер динамически отражает один из разрешённых origins, response обычно должен также учитывать кеширование:

Vary: Origin

Не отражайте произвольный Origin в Access-Control-Allow-Origin без allowlist. Конструкция «получил Origin → без проверки вернул его обратно» фактически превращает вашу политику в разрешение для любого origin.

CORS с cookies и credentials

Для cross-origin запроса с cookies frontend может использовать:

Fetch с credentials

const response = await fetch('https://api.example.com/profile', {
  credentials: 'include'
});

Сервер должен вернуть конкретный origin:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

Здесь нельзя заменить origin на *.

CORS — только одна часть работы с cookies. На фактическую отправку cookie также влияют её атрибуты, включая SameSite, Secure и доменную область.

CORS не заменяет защиту от CSRF

Это важное следствие предыдущего раздела. Сервер может принять запрос даже тогда, когда браузер позже скроет response от JavaScript.

Поэтому API с cookie-аутентификацией нельзя защищать от изменения данных только CORS-политикой. Нужны подходящие серверные меры: корректная авторизация, настройка cookie, а при соответствующей модели угроз — CSRF-защита.

Почему mode: "no-cors" обычно не исправляет CORS

Одна из популярных попыток:

fetch(url, {
  mode: 'no-cors'
});

Название создаёт впечатление, будто CORS отключится и JavaScript получит данные. Но cross-origin response в этом режиме становится opaque: JavaScript не получает нормальный доступ к body и headers.

Нужно прочитать JSON чужого API? no-cors не решает задачу.

Контролируете API? Настройте правильные response headers.

Не контролируете API? Используйте его официальный механизм доступа или собственный backend, если это допускается правилами API.

Ошибка: No Access-Control-Allow-Origin header

Сообщение:

No 'Access-Control-Allow-Origin' header is present on the requested resource

означает, что браузер не получил CORS-разрешение для текущего origin.

Проверьте:

  1. приходит ли response вообще;

  2. есть ли Access-Control-Allow-Origin;

  3. совпадает ли значение с Origin запроса;

  4. добавляется ли header также в ответы с ошибками;

  5. не удаляет ли header reverse proxy;

  6. не отвечает ли другой upstream/server block.

Ошибка: CORS preflight did not succeed

Откройте в Network именно OPTIONS. Причина иногда вообще находится ниже уровня CORS: , TLS, redirect, authentication middleware или неправильный route.

  1. OPTIONS должен доходить до нужного server/location.

  2. Preflight не должен требовать обычную пользовательскую авторизацию.

  3. Response должен содержать подходящий Access-Control-Allow-Origin.

  4. Метод actual request должен быть разрешён.

  5. Запрошенные headers должны быть разрешены.

  6. После успешного OPTIONS проверьте CORS headers ещё раз на actual response.

Ошибка: Request header field Authorization is not allowed

Если frontend отправляет:

Authorization: Bearer ...

preflight сообщает серверу о будущем header через Access-Control-Request-Headers.

Политика должна допускать его, например:

Access-Control-Allow-Headers: Content-Type, Authorization

Не добавляйте огромный список headers «на всякий случай». Разрешайте то, что действительно используется клиентом.

Mixed Content — это не ошибка CORS

Если страница загружена по HTTPS и обращается к небезопасному HTTP endpoint, браузер может заблокировать запрос как Mixed Content.

Добавление Access-Control-Allow-Origin эту проблему не исправит.

Production-решение — доступ к frontend и API по HTTPS.

Готовая настройка CORS в Express.js

Express поддерживает популярный middleware cors, который устанавливает CORS response headers.

Express: CORS для одного frontend

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

const app = express();

app.use(cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

app.get('/api/profile', (req, res) => {
  res.json({ id: 42 });
});

app.listen(3000);

При application-level app.use(cors(...)) middleware умеет обрабатывать preflight для маршрутов приложения. Если CORS назначается точечно, убедитесь, что OPTIONS также обслуживается.

Для публичного API без cookies конфигурация может быть другой. Не копируйте credentials: true автоматически: включайте credentials только там, где они действительно нужны.

Готовая настройка CORS в Nginx

Nginx может добавлять CORS headers на уровне reverse proxy. Но именно здесь часто возникают ошибки из-за обработки OPTIONS, кодов ответа и динамических origins.

Для одного известного frontend базовая схема может выглядеть так:

Nginx: один разрешённый frontend

location /api/ {
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Vary "Origin" always;

    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://app.example.com" always;
        add_header Access-Control-Allow-Credentials "true" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
        add_header Access-Control-Max-Age "600" always;
        add_header Content-Length 0;
        return 204;
    }

    proxy_pass http://backend;
}

Параметр always полезен, когда CORS header должен присутствовать не только на типичных успешных ответах. Перед копированием конфигурации определите реальные методы, headers и origin вашего приложения.

Не настраивайте CORS одновременно независимо в Nginx и backend, пока не понимаете, какой слой отвечает за политику. Дублирующиеся Access-Control-Allow-Origin headers способны сами вызвать CORS error.

Если Nginx используется не только как proxy, но и для распределения трафика, полезен отдельный материал ResizeWeb о балансировке нагрузки и настройке Nginx.

Готовая настройка CORS в FastAPI

В FastAPI используется CORSMiddleware.

FastAPI: CORS allowlist

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://app.example.com",
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

@app.get("/api/profile")
async def profile():
    return {"id": 42}

FastAPI по умолчанию использует ограничительные значения CORS-параметров, поэтому разрешения задаются явно. Для credentials также лучше использовать явный список origins вместо wildcard.

Как разрешить несколько frontend-доменов

Если есть production, staging и административная панель, политика может основываться на allowlist.

Логика:

allowedOrigins = [
  "https://app.example.com",
  "https://admin.example.com",
  "https://staging.example.com"
]

При запросе сервер сравнивает Origin со списком и только при совпадении возвращает его в Access-Control-Allow-Origin.

Проверка через простое origin.endsWith("example.com") может быть опасной: строка вроде notexample.com тоже заканчивается на example.com. Сравнивайте нормализованные origins или используйте аккуратно проверенное правило для поддоменов.

Почему CORS постоянно возникает на localhost

Типичная разработка:

http://localhost:5173  → frontend
http://localhost:3000  → API

Host одинаковый, но port различается — значит origins разные.

Есть два нормальных подхода:

  • разрешить development origin на backend;

  • настроить dev proxy в Vite/Webpack/другом dev server и обращаться к API через тот же origin.

Не переносите без необходимости development-политику вроде широкого * в production.

Как «обойти CORS», если чужой API его не разрешает

Если API принадлежит другой компании и не разрешает ваш frontend origin, JavaScript страницы не может заставить этот API изменить свою CORS-политику.

Возможные варианты:

  1. использовать официальный browser API или разрешённый origin;

  2. попросить владельца API добавить ваш origin;

  3. если условия API разрешают server-to-server доступ — выполнять запрос на своём backend;

  4. использовать официальный SDK или предусмотренную интеграционную схему.

Публичный «CORS proxy» не является нормальной production-архитектурой для токенов и пользовательских данных. Вы передаёте запрос и иногда секреты третьей стороне, которую должны отдельно оценивать с точки зрения безопасности и правил API.

Нужно ли отключать CORS в браузере

Для обычной разработки приложения — нет.

Запуск браузера с отключённой web security может временно помочь доказать, что проблема действительно связана с browser policy, но не исправляет серверную конфигурацию и не воспроизводит условия реальных пользователей.

Исправление должно работать в обычном браузере без специальных флагов.

CORS и безопасность: насколько узкой должна быть политика

MDN рекомендует разрешать минимально необходимое количество origins и ресурсов. Это хорошая production-модель: сначала определить, кому действительно нужно читать API из браузера, и только затем выдавать CORS-разрешения.

СценарийРазумная отправная политика
Публичный read-only API без credentialsВозможно *, если данные действительно публичные
SPA + private APIЯвный frontend origin
Несколько собственных frontendAllowlist
Cookies / sessionЯвный origin + credentials
Любой origin с пользовательскими даннымиНе делать так автоматически; пересмотреть модель доступа

Чек-лист грамотной настройки CORS

  1. Запишите точный frontend origin: scheme + host + port.

  2. Определите endpoints, которым действительно нужен cross-origin browser access.

  3. Разрешите только необходимые origins.

  4. Разрешите только используемые HTTP methods.

  5. Разрешите необходимые request headers.

  6. Проверьте OPTIONS отдельно от actual request.

  7. Если нужны cookies — настройте credentials и не используйте wildcard origin.

  8. При динамическом origin учитывайте Vary: Origin.

  9. Убедитесь, что CORS headers есть и на relevant error responses.

  10. Проверьте, не дублирует ли политику Nginx, или API gateway.

  11. Протестируйте из настоящего browser origin, а не только через Postman.

  12. Не используйте CORS как замену authentication, authorization и CSRF-защите.

Алгоритм диагностики CORS за несколько минут

Шаг 1. В Network найдите failing request.

Шаг 2. Скопируйте его Origin.

Шаг 3. Если есть OPTIONS — сначала исследуйте его.

Шаг 4. Сравните запрошенные method/headers с Access-Control-Allow-Methods и Access-Control-Allow-Headers.

Шаг 5. Проверьте Access-Control-Allow-Origin на response.

Шаг 6. Для cookies проверьте credentials и Access-Control-Allow-Credentials.

Шаг 7. Воспроизведите OPTIONS через curl, если подозрение падает на server/proxy.

Шаг 8. Если curl работает, а browser нет — сравните реальные headers запросов, origin и credential mode.

Именно server-side часть обычно находится в зоне ответственности backend-разработчика, а понимание того, как браузер формирует fetch() и обрабатывает response, необходимо и JavaScript-разработчику.

Коротко об истории CORS

Same-Origin Policy возникла в раннюю эпоху JavaScript как способ отделить контекст одного сайта от другого. По мере развития AJAX и web API разработчикам всё чаще требовался контролируемый cross-origin доступ.

Появлялись обходные механизмы вроде JSONP, но веб-платформе требовалась единая модель, в которой сервер мог явно дать браузеру разрешение на чтение cross-origin response.

Так сформировался CORS. Сегодня его алгоритмы определяются Fetch Standard, а браузеры применяют их автоматически при соответствующих запросах через fetch(), XMLHttpRequest и другие web APIs.

Практически полезнее помнить не даты, а архитектурную эволюцию:

Same-Origin Policy → необходимость легального cross-origin доступа → CORS headers и preflight → современная Fetch-модель

Официальная документация

Что нужно запомнить о CORS

CORS — это механизм controlled cross-origin access в браузере. Сервер через HTTP headers говорит браузеру, каким origins разрешено читать response.

Большинство проблем сводятся к пяти вещам: неправильный origin, неудачный preflight, запрещённый method, запрещённый header или неверная схема credentials.

Если видите CORS error, не начинайте с mode: "no-cors" и не отключайте безопасность браузера. Откройте Network, найдите Origin и OPTIONS, посмотрите фактические response headers и настройте allowlist на том серверном слое, который действительно отвечает клиенту.

Частые вопросы о CORS

Что такое CORS простыми словами?

CORS — правила, по которым сервер сообщает браузеру, разрешено ли JavaScript с другого origin читать его response.

Что означает Access-Control-Allow-Origin?

Это response header сервера. Он указывает origin, которому браузер может открыть доступ к cross-origin response, либо * для подходящих публичных сценариев без credentials.

Что такое preflight?

Это автоматический предварительный OPTIONS-запрос браузера. Он проверяет, разрешает ли сервер будущие method и headers до отправки actual request.

Почему CORS работает в Postman, но не в браузере?

Потому что CORS является browser-enforced механизмом. Postman, curl и server-to-server клиенты не применяют browser Same-Origin Policy.

Можно ли исправить CORS на frontend?

Обычно нет, если проблема заключается в политике чужого сервера. Разрешение приходит в response от сервера. Frontend может исправить неверный URL, лишний header или credential mode, но не способен сам добавить разрешающий response header удалённому API.

Поможет ли mode: no-cors?

Для чтения JSON API — обычно нет. Cross-origin response становится opaque, поэтому JavaScript не получает обычный доступ к body и headers.

Можно ли использовать Access-Control-Allow-Origin: *?

Да, для действительно публичных ресурсов в подходящих сценариях. Для credentialed response нужно указывать конкретный origin.

CORS защищает API от злоумышленников?

Нет. CORS ограничивает доступ browser JavaScript к response. Authentication и authorization должны выполняться отдельно на сервере.

Почему localhost:3000 и localhost:5173 считаются разными origin?

Origin включает port. Поэтому одинаковый hostname с разными портами образует разные origins.

Нужно ли разрешать OPTIONS в авторизации?

Preflight должен получить CORS-ответ до actual credentialed request. Если authentication middleware возвращает ошибку на OPTIONS раньше CORS-обработчика, браузер не сможет завершить preflight.

Редакционная проверкаРедакция ИзмерьВебМатериал сверен редакцией: проверяем факты, актуальность условий и пользу для читателя.

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

Обсудите материал, задайте вопрос или дополните его своим опытом.

Присоединитесь к обсуждению

Комментарии доступны зарегистрированным пользователям. Почту нужно подтвердить один раз.

Пока нет комментариев

Начните обсуждение: задайте вопрос автору или поделитесь практическим опытом.