CORS: що це і чому браузер блокує API | POLPROG Перейти до вмісту

CORS: що це і чому браузер блокує API

CORS не є механізмом, який просто блокує з'єднання з API. Це протокол браузера, побудований на same-origin policy, який визначає, чи може JavaScript з одного origin читати відповідь з іншого origin. В одних випадках HTTP request надсилається, а браузер лише блокує доступ скрипту до відповіді. В інших браузер спочатку надсилає preflight OPTIONS і за його результатом вирішує, чи надсилати основний request.

Опубліковано Автор Час читання 19 хв читання

CORS не є механізмом, який просто блокує з'єднання з API. Це протокол браузера, побудований на same-origin policy, який визначає, чи може JavaScript з одного origin читати відповідь з іншого origin. В одних випадках HTTP request надсилається, а браузер лише блокує доступ скрипту до відповіді. В інших браузер спочатку надсилає preflight OPTIONS і за його результатом вирішує, чи надсилати основний request.

На цій сторінці
  1. 1CORS починається з same-origin policy
  2. 2Що саме означає origin
  3. 3Що насправді означає браузер блокує API
  4. 4Прості requests: коли preflight не потрібен
  5. 5Preflight OPTIONS: що перевіряє браузер
  6. 6Найважливіші CORS headers
  7. 7`Access-Control-Allow-Origin`: wildcard чи конкретний origin
  8. 8Cookies і credentials: часте джерело проблем
  9. 9Чому `mode: "no-cors"` зазвичай нічого не виправляє
  10. 10Чому curl, Postman або backend можуть працювати
  11. 11CORS не замінює CSRF protection або authorization
  12. 12Типові помилки та діагностика
  13. 13Небезпечні CORS-конфігурації
  14. 14Продуктивність: вартість preflight і `Access-Control-Max-Age`
  15. 15Практична конфігурація та checklist deployment

CORS починається з same-origin policy

Same-origin policy є базовим механізмом безпеки браузера. Він обмежує можливість документа або скрипту читати дані з іншого origin і, зокрема, не дозволяє шкідливому сайту читати дані із сервісу, де користувач уже авторизований. [3]

CORS, Cross-Origin Resource Sharing, є HTTP-механізмом, який дозволяє серверу контрольовано послабити це правило та явно визначити origin, яким можна читати відповідь. Fetch Standard описує CORS як opt-in protocol. [1][2]

Що саме означає origin

СторінкаЦільова URLСпіввідношенняЧому
https://app.example.comhttps://app.example.com/apiТой самий originТі самі schema, host і port
https://app.example.comhttps://api.example.comІнший originІнший host
https://app.example.comhttp://app.example.comІнший originІнша schema
https://app.example.comhttps://app.example.com:8443Інший originІнший port
https://app.example.comhttps://app.example.com/v2Той самий originЗмінюється лише path

Origin визначається схемою, host і port. Дві URL є same-origin лише тоді, коли збігаються всі три значення. Path не має значення. [3][4]

`https://app.example.com` і `https://api.example.com` тому є різними origin. Так само HTTP проти HTTPS або різні ports. [4]

Same-origin і same-site не є однаковими поняттями. Це особливо важливо для cookies, бо `SameSite` використовує поняття site, а CORS працює з origin. [13]

Що насправді означає браузер блокує API

Фраза про те, що браузер блокує request, часто надто спрощує ситуацію. Для простого cross-origin request браузер може надіслати request, отримати коректну HTTP-відповідь, а потім не віддати її JavaScript, якщо CORS check не пройдено. [1][6]

Для requests із preflight послідовність інша. Спочатку надсилається `OPTIONS`. Якщо preflight не пройдено, основний request не надсилається. [2][5][6]

Ця різниця важлива для операцій, що змінюють дані. CORS error не означає автоматично, що backend не отримав жодного request.

Прості requests: коли preflight не потрібен

MethodПриклад headersПростий requestРезультат
GETAcceptТакБез preflight
POSTContent-Type: text/plainТакБез preflight
POSTContent-Type: application/jsonНіJSON запускає preflight
PUTContent-Type: application/jsonНіMethod запускає preflight
GETAuthorization: Bearer ...НіHeader запускає preflight

Request є простим лише тоді, коли відповідає CORS safelist. Дозволені methods: `GET`, `HEAD` і `POST`. Headers, встановлені вручну, мають бути safelisted, а `Content-Type` може бути лише `application/x-www-form-urlencoded`, `multipart/form-data` або `text/plain`. [1]

Звичайний `POST` із `Content-Type: application/json` не є простим і зазвичай запускає preflight. Те саме стосується `PUT`, `DELETE` або custom header на кшталт `X-Request-ID`. [1][11]

Preflight OPTIONS: що перевіряє браузер

Preflight є автоматичним `OPTIONS` request. Браузер надсилає `Origin`, `Access-Control-Request-Method` і за потреби `Access-Control-Request-Headers`. Сервер відповідає дозволеними origin, methods і headers. [2][5]

Якщо застосунок хоче надіслати `DELETE` з `Authorization`, браузер може спочатку перевірити, чи дозволено цьому origin використовувати такий method і header. Лише позитивна відповідь дозволяє основний request. [5]

Preflight додає ще один network round trip, але його результат можна зберігати в окремому CORS preflight cache, незалежному від звичайного HTTP cache. [5][10]

Найважливіші CORS headers

HeaderНапрямПризначення
OriginRequestOrigin, що ініціює request
Access-Control-Allow-OriginResponseOrigin, коду якого можна віддати response
Access-Control-Allow-MethodsPreflight responseMethods, дозволені preflight
Access-Control-Allow-HeadersPreflight responseRequest headers, дозволені preflight
Access-Control-Allow-CredentialsResponseДозвіл передати response із credentials
Access-Control-Expose-HeadersResponseДодаткові response headers, доступні JavaScript
Access-Control-Max-AgePreflight responseТривалість cache результату preflight
Vary: OriginResponseПовідомляє cache, що response залежить від Origin

`Origin` є request header, що вказує origin, який ініціює request. `Access-Control-Allow-Origin` є server response header, який визначає, чи може браузер передати відповідь коду з цього origin. [1][2][8]

`Access-Control-Allow-Methods` і `Access-Control-Allow-Headers` особливо важливі для preflight. `Access-Control-Allow-Credentials` дозволяє передати відповідь із credentials, а `Access-Control-Expose-Headers` робить додаткові response headers доступними JavaScript. [1][2]

Якщо сервер динамічно змінює `Access-Control-Allow-Origin`, MDN рекомендує також `Vary: Origin`. [1]

`Access-Control-Allow-Origin`: wildcard чи конкретний origin

`Access-Control-Allow-Origin: *` дозволяє ділитися відповіддю з будь-яким origin для requests без credentials. Для справді публічного API це може бути правильно. [2][8]

Для приватних або чутливих endpoints набір дозволених origin має бути мінімальним. MDN і OWASP рекомендують конкретні origin, якщо глобальний доступ не потрібен. [14][17]

Якщо дозволено кілька origin, сервер має порівнювати вхідний `Origin` з allowlist і повертати лише перевірене значення. Сліпе віддзеркалення будь-якого origin є небезпечним. [8][16]

Cookies і credentials: часте джерело проблем

Fetch може використовувати `credentials: "include"` для credentials у cross-origin requests. Цього недостатньо. Сервер має повернути `Access-Control-Allow-Credentials: true` і конкретний `Access-Control-Allow-Origin`. `*` у такому випадку не дозволений. [2][6][9]

Cookies також мають власні правила. `SameSite` може не дозволити cookie надсилатися в cross-site request, а `SameSite=None` вимагає `Secure`. [13]

CORS і `SameSite` вирішують різні проблеми. CORS може бути налаштований правильно, але cookie не надсилається, або cookie може надсилатися, а CORS все одно блокує доступ JavaScript до відповіді.

Чому `mode: "no-cors"` зазвичай нічого не виправляє

`fetch(..., { mode: "no-cors" })` не обходить CORS, якщо застосунок має читати API response. Methods і headers обмежуються, а відповідь є opaque. JavaScript не може читати body або headers, а видимий status дорівнює `0`. [6][7][11]

`no-cors` має спеціалізовані сценарії, зокрема деякі випадки Service Worker, але для звичайного JSON API це зазвичай неправильне рішення. [6]

Якщо зовнішнє API не підтримує CORS, власний backend або proxy може виконати server-to-server request. [11][12]

Чому curl, Postman або backend можуть працювати

Same-origin policy і CORS забезпечуються браузером. HTTP-клієнт поза цією моделлю не проходить такий самий CORS check. Тому endpoint може працювати в curl або API tool, але блокуватися для JavaScript на сторінці. [2][3][16]

Це також показує, чому CORS не є authentication для API. Клієнт поза браузером може встановити власний `Origin`, тому OWASP не радить використовувати цей header як доказ identity. [16]

API все одно потребує authorization, tokens, sessions, permission checks і server-side validation.

CORS не замінює CSRF protection або authorization

Same-origin policy переважно обмежує cross-origin reads. Forms і деякі прості requests все одно можуть надсилатися cross-origin, тому застосунки на cookies потребують CSRF protection. [3][15]

OWASP прямо рекомендує не покладатися лише на CORS або `Origin` для access control до чутливих resources. Authentication і authorization потрібні незалежно від CORS. [16]

CORS відповідає на питання, чи може браузер віддати response коду з певного origin. Він не визначає, чи має користувач або клієнт право виконувати операцію.

Типові помилки та діагностика

Відсутній `Access-Control-Allow-Origin` є однією з найпоширеніших помилок. Інші причини: заборонений method, відсутній header у `Access-Control-Allow-Headers`, `*` з credentials, неправильна OPTIONS-відповідь або redirects у CORS flow. [11][12]

JavaScript навмисно отримує мало деталей. Console і Network panel у DevTools корисніші для точної діагностики. [1][11]

`CORS request did not succeed` також може бути спричинений DNS, timeout, connection refused, TLS, mixed content або browser extensions. [11]

  • Перевірте точний frontend origin: schema, host і port.
  • Перевірте `OPTIONS`, якщо він є.
  • Перевірте status і `Access-Control-Allow-*` у preflight response.
  • Перевірте, що основна response також має потрібні CORS headers.
  • Для cookies перевірте `credentials`, `Access-Control-Allow-Credentials`, `SameSite` і `Secure`.
  • Перевірте redirects, TLS, mixed content і network errors.
  • Порівняйте browser request із робочим curl або API-tool request.

Небезпечні CORS-конфігурації

Сліпе повернення вхідного `Origin` небезпечне, якщо requests із credentials повертають чутливі дані. Зловмисник може контролювати власний origin і намагатися читати responses у контексті авторизованого користувача. [16]

Надто широкі regular expressions і довіра всім subdomains також ризиковані, якщо одну з них можна захопити. OWASP рекомендує точний allowlist. [16][17]

`Access-Control-Allow-Origin: *` не є автоматичною вразливістю для повністю публічних даних без credentials. Проблемою є політика, ширша за реальну потребу. [8][17]

Продуктивність: вартість preflight і `Access-Control-Max-Age`

Preflight додає додатковий `OPTIONS` перед основним request і може збільшити latency. Його результат можна cache за допомогою `Access-Control-Max-Age` в окремому preflight cache. [5][10]

MDN вказує default 5 секунд без header. Браузери мають власні максимуми. Firefox обмежує до 86400 секунд, Chromium від версії 76 до 7200 секунд. [10]

Стабільні policies варто cache, але не настільки довго, щоб помилково надто широку політику було важко швидко відкликати.

Практична конфігурація та checklist deployment

Спочатку вирішіть, чи endpoint взагалі має бути доступним cross-origin. Якщо ні, CORS headers не потрібні. Якщо так, визначте allowlist origin, потрібні methods і headers та необхідність credentials. [14][17]

Для dynamic allowlist повертайте лише перевірений origin і додавайте `Vary: Origin`. З credentials використовуйте конкретний origin та `Access-Control-Allow-Credentials: true`. Frontend не може замінити це рішення сервера. [1][2]

Після deployment протестуйте простий request, preflight, сценарій з cookie, заборонений origin і реальні errors у DevTools.

  • Чи справді потрібен cross-origin доступ?
  • Які точні origin мають бути дозволені?
  • Чи потрібні credentials?
  • Які methods і request headers потрібні?
  • Чи доходить `OPTIONS` до застосунку і чи відповідає коректно?
  • Чи має основна response правильні CORS headers?
  • Чи містять dynamic responses `Vary: Origin`?
  • Чи endpoint все ще вимагає звичайні authentication і authorization?
  • Чи протестована політика з дозволеним і забороненим origin?

CORS слід розуміти як правило браузера щодо доступу до відповідей, а не як універсальну межу безпеки API. Спочатку визначте origin frontend і API, потім з'ясуйте, чи request є простим або потребує preflight, після чого перевірте OPTIONS і CORS headers основної відповіді. Безпечна конфігурація дозволяє лише потрібні origin, methods і headers, а authentication та authorization залишаються на рівні застосунку.

CORS Same-Origin Policy Web Security HTTP API Fetch API Preflight Cookies CSRF Frontend

Часті запитання

Що таке CORS?

CORS є HTTP-протоколом, який браузери використовують для контрольованого обміну responses між різними origin. Сервер через headers визначає, які origin можуть читати response. [1][2]

Чому API працює в Postman, але не в браузері?

Тому що CORS забезпечується браузером як частина same-origin policy. HTTP-клієнти поза браузером не підпадають під такий самий контроль. [2][3][16]

Чи CORS завжди блокує надсилання request?

Ні. Простий request може бути надісланий, а браузер лише заблокує JavaScript доступ до response. Якщо обов'язковий preflight не пройдено, основний request не надсилається. [1][5][6]

Що запускає preflight?

Наприклад methods поза GET, HEAD, POST, custom headers або Content-Type: application/json. Браузер спочатку надсилає OPTIONS. [1][5]

Чи треба додавати Access-Control-Allow-Origin у frontend?

Ні. Це server response header. Frontend не може сам надати собі цей дозвіл. [1][8][12]

Чи mode: "no-cors" виправляє CORS?

Ні для звичайного API, дані якого треба читати. Response стає opaque, а body, headers і звичайний status недоступні. [6][7]

Чи можна використовувати Access-Control-Allow-Origin: *?

Так для публічних responses без credentials. Ні для responses із credentials. [2][8][9]

Як CORS працює з cookies?

Frontend часто використовує credentials: "include", а сервер має повернути конкретний origin і Access-Control-Allow-Credentials: true. Cookie також підпадає під SameSite і Secure. [6][9][13]

Чи CORS захищає API від curl або bot?

Ні. CORS обмежує browser JavaScript. Зовнішній клієнт може надіслати власний request і Origin, тому API все одно потребує authorization. [16]

Чи CORS захищає від CSRF?

Не повністю. Застосунки на cookies все одно потребують CSRF protection. [3][15][16]

Для чого Vary: Origin?

Коли Access-Control-Allow-Origin змінюється динамічно, Vary: Origin повідомляє cache, що response залежить від origin запиту. [1]

Як зменшити кількість preflight?

Результат можна cache через Access-Control-Max-Age або використовувати прості requests, якщо це доречно функціонально і з точки зору безпеки. [1][10][11]

Джерела та примітки

  1. MDN, Cross-Origin Resource Sharing (CORS)123456789101112131415
  2. WHATWG, Fetch Standard, CORS protocol123456789101112
  3. MDN, Same-origin policy123456
  4. MDN, Origin12
  5. MDN, Preflight request1234567
  6. MDN, Using the Fetch API12345678
  7. MDN, RequestInit12
  8. MDN, Access-Control-Allow-Origin123456
  9. MDN, Access-Control-Allow-Credentials123
  10. MDN, Access-Control-Max-Age1234
  11. MDN, CORS errors1234567
  12. MDN, CORS header Access-Control-Allow-Origin missing123
  13. MDN, Set-Cookie123
  14. MDN, Practical CORS configuration12
  15. MDN, Cross-site request forgery (CSRF)12
  16. OWASP, HTML5 Security Cheat Sheet, Cross Origin Resource Sharing123456789
  17. OWASP, HTTP Headers Cheat Sheet, Access-Control-Allow-Origin1234

Чи було це корисно?

Отримуйте нові статті електронною поштою

Один короткий лист на кожну нову статтю Навчання. Без спаму, відписка в один клік.

Ми використовуємо вашу пошту лише для надсилання нових статей. Без передачі третім сторонам.

Назад до Навчання