Menu

CORS w Express: jak naprawić błąd CORS (z cors i bez)

Błąd CORS oznacza, że przeglądarka nie pozwoliła stronie odczytać odpowiedzi twojego API, bo odpowiedź nie dopuszczała originu tej strony. Naprawiasz to na serwerze: wysyłasz Access-Control-Allow-Origin i odpowiadasz na preflight OPTIONS, ręcznie albo pakietem cors.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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łówekZnaczenie
Access-Control-Allow-OriginJedyny origin, który może odczytać odpowiedź, albo * dla dowolnego
Access-Control-Allow-MethodsMetody dozwolone w właściwym żądaniu (tylko preflight)
Access-Control-Allow-HeadersNagłówki żądania, które strona może wysłać, np. Authorization (tylko preflight)
Access-Control-Allow-Credentialstrue, jeśli strona może wysłać ciasteczka i odczytać odpowiedź
Access-Control-Max-AgeIle sekund przeglądarka może cache'ować odpowiedź na preflight
Vary: OriginMó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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

Zacznij