Błąd CORS ("No 'Access-Control-Allow-Origin' header is present") oznacza, że strona z jednego originu, np. http://localhost:5173, wywołała twoje API na innym originie, a przeglądarka nie pozwoliła stronie odczytać odpowiedzi, bo odpowiedź nie mówiła, że ten origin jest dozwolony. Naprawa jest po stronie serwera: wyślij Access-Control-Allow-Origin z originem strony i odpowiedz na preflight OPTIONS przeglądarki metodami i nagłówkami, które akceptujesz. W Express to kilka linii middleware albo app.use(cors()) z pakietu cors.
Odpowiedź to lista produktów, a jej zakładka Headers pokazuje access-control-allow-origin: https://app.example.com: strona z tego originu może ją odczytać. Teraz wyślij OPTIONS /api/products/1 z nagłówkami Origin: https://app.example.com i Access-Control-Request-Method: PUT. To preflight, który przeglądarka wysyła przed PUT, a odpowiedź to 204 z dozwolonymi metodami i nagłówkami, bez body.
Co sprawdza przeglądarka
Strony z tego samego originu niczego nie potrzebują. Przy żądaniu cross-origin przeglądarka czyta te nagłówki odpowiedzi:
| Nagłówek | Znaczenie |
|---|---|
Access-Control-Allow-Origin | Jedyny origin, który może odczytać odpowiedź, albo * dla dowolnego |
Access-Control-Allow-Methods | Metody dozwolone w właściwym żądaniu (tylko preflight) |
Access-Control-Allow-Headers | Nagłówki żądania, które strona może wysłać, np. Authorization (tylko preflight) |
Access-Control-Allow-Credentials | true, jeśli strona może wysłać ciasteczka i odczytać odpowiedź |
Access-Control-Max-Age | Ile sekund przeglądarka może cache'ować odpowiedź na preflight |
Vary: Origin | Mówi cache'om, że odpowiedź zależy od nagłówka żądania Origin |
Prosty GET albo POST jak z formularza trafia prosto do serwera, a przeglądarka sprawdza Access-Control-Allow-Origin w odpowiedzi. Serwer i tak wykonuje żądanie; CORS decyduje tylko o tym, czy strona zobaczy wynik. Żądanie z body w JSON, nagłówkiem Authorization albo metodą taką jak PUT lub DELETE poprzedza preflight OPTIONS, a właściwe żądanie idzie tylko wtedy, gdy odpowiedź na preflight na to pozwala. Dlatego middleware CORS musi odpowiedzieć na OPTIONS, zanim zadziała jakakolwiek trasa czy sprawdzanie autoryzacji.
Kilka originów i credentials
Access-Control-Allow-Origin zawiera jeden origin albo *, nigdy listę. Żeby dopuścić kilka, porównaj nagłówek Origin żądania z listą dozwolonych i odeślij go, gdy pasuje:
Pierwsze żądanie nie ma nagłówka Origin, tak jak wywołanie z curl albo ze strony z tego samego originu, więc dostaje {"origin":null} i żadnych nagłówków CORS. Wyślij GET /api/me z nagłówkiem Origin: http://localhost:5173, a zakładka Headers pokaże ten origin odesłany z powrotem plus access-control-allow-credentials: true. Zmień nagłówek na Origin: https://evil.example i oba nagłówki CORS znikną: serwer nadal odpowiada, ale przeglądarka nie pozwoliłaby stronie odczytać odpowiedzi. Access-Control-Allow-Credentials: true pozwala stronie wysyłać ciasteczka (przez fetch(url, { credentials: 'include' })), a przeglądarki odrzucają to razem z *, więc credentials zawsze wymagają dokładnego originu.
Pakiet cors
Pakiet cors robi to samo przez opcje. Zainstaluj go przez npm install cors:
const cors = require('cors');
// Any origin, no cookies: fine for a public read-only API.
app.use(cors());
// Only your frontend, with cookies.
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
// One route only.
app.get('/api/public', cors(), handler);
Wywołany bez opcji cors() wysyła Access-Control-Allow-Origin: * i odpowiada na każdy preflight. Używaj * tylko dla publicznych danych, które nie wymagają logowania.
Częste błędy
Dodawanie CORS po trasach albo po middleware autoryzacji. Preflight OPTIONS nie ma nagłówka Authorization, więc sprawdzanie autoryzacji ustawione wcześniej odpowiada 401, a przeglądarka zgłasza błąd CORS. Zarejestruj middleware CORS jako pierwszy.
Ustawianie * razem z credentials. Przeglądarki odrzucają taką odpowiedź. Odsyłaj konkretny origin.
Naprawianie CORS we frontendzie. Opcje żądania takie jak mode: 'no-cors' nie znoszą ograniczenia; dają nieprzezroczystą odpowiedź, której nie odczytasz. Pomagają tylko nagłówki z serwera.
Jak działa app.use() i kolejność middleware, przeczytasz w Express middleware.
Najczęściej zadawane pytania
Dlaczego moje API działa w Postmanie lub curl, ale nie w przeglądarce?
CORS egzekwują tylko przeglądarki. Postman, curl i inne serwery ignorują nagłówki CORS, więc i tak czytają odpowiedź. Przeglądarka nie przekaże odpowiedzi skryptom strony z innego originu, dopóki serwer na ten origin nie pozwoli.
Czy CORS to zabezpieczenie mojego serwera?
Chroni użytkowników, nie twój serwer. Nie pozwala złośliwej stronie czytać odpowiedzi twojego API z ciasteczkami użytkownika. Każdy nadal może wywołać twoje API ze skryptu albo z serwera, więc uwierzytelnianie i autoryzacja to dalej twoje zadanie.
Co liczy się jako inny origin?
Origin to schemat, host i port razem. http://localhost:5173 i http://localhost:3000 to różne originy, bo różnią się portem, a https://example.com i https://api.example.com różnią się hostem.
Czy da się uniknąć CORS podczas developmentu?
Tak, jeśli żądanie będzie same-origin: skonfiguruj serwer deweloperski frontendu (np. server.proxy w Vite), żeby przekazywał /api do serwera Express. Przeglądarka rozmawia wtedy z jednym originem i nagłówki CORS nie są potrzebne.