Як виправити помилки CORS (і чому вони виникають)
Помилка CORS означає, що ваша веб-сторінка намагалася прочитати відповідь з іншого origin (іншого домену, порту або протоколу), а сервер не надіслав заголовків, які це дозволяють. Браузери блокують відповідь, щоб захистити користувачів. Виправляють це на сервері: повертаючи заголовок Access-Control-Allow-Origin для origin вашого сайту та обробляючи OPTIONS-запити preflight, або обходять проблему, надсилаючи запит через власний бекенд на тому самому origin.
Це одна з найзаплутаніших помилок у веб-розробці, бо запит часто доходить до сервера без проблем. Цей посібник пояснює, що відбувається, і як виправити кожен варіант.
Що означає «origin»
Origin — це поєднання протоколу, домену та порту:
| URL | Origin |
|---|---|
https://myapp.com/dashboard | https://myapp.com |
https://api.myapp.com/users | https://api.myapp.com (інший піддомен, тобто інший origin) |
http://localhost:5173 | http://localhost:5173 |
http://localhost:3000 | http://localhost:3000 (інший порт, тобто інший origin) |
Браузери дотримуються same-origin policy (політики одного походження): JavaScript на сторінці може вільно читати відповіді зі свого origin, але не з інших. CORS (Cross-Origin Resource Sharing, спільний доступ до ресурсів між різними джерелами) — це механізм, який дозволяє серверу свідомо погодитися: «відповіді від мене можуть читати сторінки з цих origin».
Два наслідки часто дивують:
- Запит часто доходить до сервера. CORS зазвичай блокує вашому коду читання відповіді, а не отримання запиту сервером. POST могло зберегтися, навіть якщо ви побачили помилку.
- CORS застосовують лише браузери. curl, Postman і серверний код його ігнорують. Тому «працює в Postman» нічого не говорить про CORS.
Крок 1: Прочитайте точний текст помилки
Відкрийте інструменти розробника (правий клік → Inspect, «Перевірити»), перейдіть на вкладку Console і прочитайте повне повідомлення. Формулювання Chrome показує, яка саме проблема:
| Повідомлення містить | Що не так |
|---|---|
No 'Access-Control-Allow-Origin' header is present | Сервер не надіслав заголовок взагалі, або запит впав ще до вашого коду CORS (наприклад, 404 чи 500) |
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin | Сервер дозволяє інший origin, ніж ваш |
Response to preflight request doesn't pass access control check | Попередній запит OPTIONS (preflight) не пройшов перевірку |
...must not be the wildcard '*' when the request's credentials mode is 'include' | Ви надсилаєте cookies, а сервер відповів * |
Request header field authorization is not allowed | Сервер не додав цей заголовок до Access-Control-Allow-Headers |
Потім відкрийте вкладку Network (Мережа), знайдіть запит, що не вдався (і будь-які OPTIONS-запити перед ним), і подивіться на код статусу та заголовки відповіді.
Спершу перевірте код статусу. Багато «помилок CORS» насправді є серверною помилкою під маскою. Якщо сервер падає або повертає 404, його відповідь часто не містить заголовків CORS, тож браузер показує CORS замість справжньої проблеми. Виправте 500 або 404, і помилка CORS може зникнути.
Крок 2: Вирішіть, чи взагалі варто викликати цей API з браузера
Перш ніж додавати заголовки, запитайте себе: чи має браузер взагалі звертатися до цього API напряму?
- Стороннє API, якому потрібен секретний ключ (OpenAI, секретний ключ Stripe, більшість сервісів email), ніколи не слід викликати з браузера. Ключ побачить кожен відвідувач. Викликайте його з бекенду, і проблема CORS зникне, бо запити між серверами не підлягають CORS. Див. як зберігати ключі API в безпеці.
- Стороннє API, яким ви не керуєте, що не надсилає заголовків CORS, не можна виправити з вашого боку. Викликайте його з бекенду.
- Ваше власне API можна налаштувати правильно. Продовжуйте нижче.
Крок 3: Виправте на сервері
Сервер має додавати правильні заголовки до всіх своїх відповідей, включно з відповідями з помилками.
Для самого запиту надішліть:
Access-Control-Allow-Origin: https://myapp.com(точний origin вашого фронтенду, без кінцевого слеша)Vary: Origin, якщо значення залежить від того, який origin звернувся
Для preflight-запиту OPTIONS відповідайте успішним статусом (200 або 204) і додайте:
Access-Control-Allow-Origin: https://myapp.comAccess-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETEAccess-Control-Allow-Headers: Content-Type, Authorization(кожен власний заголовок, який ви надсилаєте)- За бажанням
Access-Control-Max-Age: 600, щоб браузер кешував preflight-відповідь певний час
Більшість фреймворків мають пакет або налаштування для цього. В Express це пакет cors, у FastAPI — CORSMiddleware, в ASP.NET Core — AddCors, у Spring — @CrossOrigin або конфігурація CORS. Типове налаштування для Express:
import cors from "cors";
app.use(cors({
origin: ["https://myapp.com", "http://localhost:5173"],
credentials: true,
}));
Розміщуйте обробку CORS перед своїми маршрутами та автентифікацією, щоб preflight-запити (які не містять входу) не відхилялися з помилкою 401.
Працюючи з конструктором на основі ШІ, опишіть налаштування точно:
Фронтенд на
https://myapp.comвикликає API наhttps://api.myapp.comі отримує помилку «Response to preflight request doesn't pass access control check». Налаштуй CORS в API так, щоб дозволялися самеhttps://myapp.comі origin превью, дозволь методи GET, POST, PATCH, DELETE із заголовками Content-Type і Authorization, обробляй OPTIONS до автентифікації та додавай заголовки CORS також до відповідей з помилками.
Крок 4: Обробіть cookies і облікові дані
Якщо ваш фронтенд надсилає cookies (наприклад, сесію входу) на інший origin, обидві сторони мають узгодити налаштування:
- Фронтенд встановлює
credentials: "include"уfetchабоwithCredentials: trueв axios. - Сервер надсилає
Access-Control-Allow-Credentials: true. Access-Control-Allow-Originна сервері має бути точним origin, а не*.
Cookies між сайтами також потребують SameSite=None; Secure, а деякі браузери додатково обмежують сторонні cookies. Це ще одна причина віддати перевагу підходу з кроку 5.
Крок 5: Уникніть CORS, налаштувавши один origin
Часто найчистіше рішення — взагалі не робити міжсайтових запитів. Віддавайте API з того самого origin, що й сторінку, наприклад за адресою https://myapp.com/api/...:
- У розробці більшість dev-серверів уміють проксіювати. Налаштування
server.proxyу Vite може переспрямовувати/apiна ваш бекенд на іншому порту, тож браузер взаємодіє лише з одним origin. - У продакшені спрямуйте
/apiна бекенд через правила rewrite або проксі вашого хостингу, або віддавайте фронтенд і API з одного сервера.
Тоді фронтенд викликає відносний шлях, наприклад fetch("/api/orders"). Немає міжсайтового запиту, немає preflight між різними origin і немає клопоту з cookies. Крім того, не потрібно жорстко прописувати домен API, який відрізняється між превью та продакшеном.
Змініть фронтенд так, щоб він викликав бекенд через відносні шляхи
/apiзамість повної URL-адреси, і налаштуйте dev-проксі, щоб/apiпереспрямовувався на сервер API. Потім приберіть налаштування CORS, які більше не потрібні.
Чого не варто робити
mode: "no-cors"уfetch. Це нічого не обходить: ваш код отримає порожню, нечитабельну відповідь.- Розширення браузера, що вимикають CORS. Вони змінюють лише ваш браузер. Кожен відвідувач однаково побачить помилку.
- Публічні «CORS-проксі» для справжніх застосунків. Вони можуть читати й змінювати все, що через них проходить, і самі часто перестають працювати.
Access-Control-Allow-Origin: *для API, що використовує входи. Це й так не працює з обліковими даними, і це неправильний вибір за замовчуванням для приватних даних.- Відбиття будь-якого заголовка
Originназад з дозволеними обліковими даними. Це дає будь-якому сайту робити запити від імені ваших користувачів, які ввійшли. Використовуйте список дозволених origin.
Типові причини в застосунках, створених з ШІ
| Ситуація | Виправлення |
|---|---|
| Фронтенд викликає стороннє API з секретним ключем | Перенесіть виклик на бекенд |
| Працює в превью, не працює на опублікованому домені | Додайте опубліковані origin і origin власного домену до списку дозволених або використовуйте відносні шляхи /api |
| Помилка лише на POST, PUT або DELETE | Preflight не оброблено; додайте обробку OPTIONS і дозволені заголовки |
| Помилка з'явилася після додавання входу | Облікові дані потребують точного origin і Allow-Credentials |
| «Помилка CORS», але у вкладці Network показано 500 | Спершу виправте помилку сервера |
URL API жорстко прописано як localhost | Використовуйте відносні шляхи або налаштування для кожного середовища |
CORS у Mythex
Коли в проєкті Mythex є і сайт, і сервер API, обидва працюють як сервіси в одному проєкті, і ви можете перемикатися між ними в превью. Якщо попросити агента, щоб фронтенд викликав бекенд через відносний шлях /api, який у розробці проксіюється, це усуває більшість проблем CORS. Якщо ж вам потрібні міжсайтові виклики, дайте агенту точний текст помилки з консолі та задіяні origin. Перевірте результат і в опублікованому застосунку, бо його адреса відрізняється від превью. Див. Multi-service apps і API backends у документації, а також наші посібники як налагоджувати застосунок, створений з ШІ і фронтенд чи бекенд.
Чекліст CORS
- Прочитано повний текст помилки в консолі та код статусу невдалого запиту
- Виключено приховану 404 або 500
- API з секретними ключами викликаються з бекенду, а не з браузера
- Сервер надсилає
Access-Control-Allow-Originз точними дозволеними origin - Preflight
OPTIONSвідповідає дозволеними методами та заголовками, до автентифікації - Заголовки CORS присутні також у відповідях з помилками
- Облікові дані: точний origin,
Allow-Credentials: true, правильні налаштування cookies - Охоплено превью, опублікований домен і власний домен, або використано відносні шляхи
/api - Немає
no-cors, публічних проксі чи обхідних шляхів із відбиттям будь-якого origin
Запитання
Що означає помилка CORS?
Ваша сторінка, завантажена з одного origin, намагалася прочитати відповідь з іншого origin, а відповідь того сервера не містила заголовків, які це дозволяють. Браузер блокує читання відповіді вашим кодом. Виправлення — на сервері або в тому, як маршрутизується запит, а не у фронтенд-коді.
Чи можна виправити помилку CORS з фронтенду?
Не належним чином. Такі налаштування, як mode: 'no-cors', лише приховують відповідь від вашого коду. Виправляється це тим, що сервер надсилає правильний заголовок Access-Control-Allow-Origin, або викликом API з власного бекенду чи проксі на тому самому origin.
Чи безпечний Access-Control-Allow-Origin: *?
Це нормально для публічних даних, які може читати будь-хто, наприклад для публічного API без входу. Його не можна використовувати з cookies чи іншими обліковими даними, а для приватних API слід вказувати точні дозволені origin.
Чому запит працює в Postman або curl, але не в браузері?
CORS застосовується лише браузерами. Такі інструменти, як Postman і curl, його не застосовують, тож запит може успішно пройти там і все одно бути заблокованим, коли його виконує JavaScript сторінки.
Що таке preflight-запит?
Для запитів, які не є простими, наприклад JSON POST або запит із заголовком Authorization, браузер спочатку надсилає OPTIONS-запит і питає сервер, які методи й заголовки дозволені. Якщо цей OPTIONS-запит не вдається або не має потрібних заголовків, основний запит не надсилається взагалі.