CORS: czym jest i dlaczego przeglądarka blokuje API | POLPROG Przejdź do treści

CORS: czym jest i dlaczego przeglądarka blokuje API

CORS nie jest mechanizmem, który po prostu blokuje połączenia z API. Jest protokołem przeglądarkowym opartym na polityce tego samego źródła, który decyduje, czy JavaScript z jednego originu może odczytać odpowiedź z innego originu. W części przypadków żądanie HTTP zostaje wysłane, a przeglądarka blokuje jedynie dostęp skryptu do odpowiedzi. W innych przypadkach najpierw wysyłany jest preflight OPTIONS i dopiero jego wynik decyduje, czy właściwe żądanie zostanie wykonane.

Opublikowano Autor Czas czytania 19 min czytania

CORS nie jest mechanizmem, który po prostu blokuje połączenia z API. Jest protokołem przeglądarkowym opartym na polityce tego samego źródła, który decyduje, czy JavaScript z jednego originu może odczytać odpowiedź z innego originu. W części przypadków żądanie HTTP zostaje wysłane, a przeglądarka blokuje jedynie dostęp skryptu do odpowiedzi. W innych przypadkach najpierw wysyłany jest preflight OPTIONS i dopiero jego wynik decyduje, czy właściwe żądanie zostanie wykonane.

Na tej stronie
  1. 1CORS zaczyna się od polityki tego samego źródła
  2. 2Co dokładnie oznacza origin
  3. 3Co naprawdę znaczy komunikat, że przeglądarka blokuje API
  4. 4Proste żądania: kiedy przeglądarka pomija preflight
  5. 5Preflight OPTIONS: co sprawdza przeglądarka
  6. 6Najważniejsze nagłówki CORS
  7. 7`Access-Control-Allow-Origin`: `*` czy konkretny origin
  8. 8Cookies i dane uwierzytelniające: najczęstsze źródło problemów
  9. 9Dlaczego `mode: "no-cors"` zwykle niczego nie naprawia
  10. 10Dlaczego curl, Postman albo backend działają, a przeglądarka nie
  11. 11CORS nie zastępuje CSRF ani autoryzacji
  12. 12Najczęstsze błędy i jak je diagnozować
  13. 13Niebezpieczne konfiguracje CORS
  14. 14Wydajność: koszt preflight i `Access-Control-Max-Age`
  15. 15Praktyczna konfiguracja i checklista wdrożeniowa

CORS zaczyna się od polityki tego samego źródła

Polityka tego samego źródła, czyli same-origin policy, jest podstawowym mechanizmem bezpieczeństwa przeglądarki. Ogranicza możliwość odczytywania przez dokument lub skrypt danych pochodzących z innego originu. Jej celem jest między innymi uniemożliwienie złośliwej stronie odczytania danych z serwisu, w którym użytkownik jest zalogowany. [3]

CORS, czyli Cross-Origin Resource Sharing, jest mechanizmem opartym na HTTP, który pozwala serwerowi poluzować tę regułę i jawnie wskazać, które originy mogą odczytywać daną odpowiedź. Fetch Standard opisuje CORS jako protokół wymagający jawnej zgody serwera. [1][2]

Co dokładnie oznacza origin

StronaAdres docelowyRelacjaDlaczego
https://app.example.comhttps://app.example.com/apiTen sam originTen sam schemat, host i port
https://app.example.comhttps://api.example.comInny originInny host
https://app.example.comhttp://app.example.comInny originInny schemat
https://app.example.comhttps://app.example.com:8443Inny originInny port
https://app.example.comhttps://app.example.com/v2Ten sam originZmienia się tylko ścieżka

Origin tworzą trzy elementy: schemat, host i port. Dwa adresy mają ten sam origin wyłącznie wtedy, gdy wszystkie trzy wartości są zgodne. Sama ścieżka URL nie ma znaczenia. [3][4]

Dlatego `https://app.example.com` i `https://api.example.com` mają różne originy mimo wspólnej domeny bazowej. Również `http://app.example.com` i `https://app.example.com` są różne z powodu schematu, a porty 443 i 8443 tworzą różne originy. [4]

Pojęcia same-origin i same-site nie są równoważne. Ma to znaczenie szczególnie przy cookies, gdzie atrybut `SameSite` działa według pojęcia site, a CORS według originu. [13]

Co naprawdę znaczy komunikat, że przeglądarka blokuje API

Sformułowanie, że przeglądarka blokuje żądanie, jest często zbyt dużym uproszczeniem. Dla prostych żądań cross-origin przeglądarka może wysłać żądanie do serwera, otrzymać poprawną odpowiedź HTTP, a następnie nie udostępnić tej odpowiedzi kodowi JavaScript, jeżeli kontrola CORS zakończy się niepowodzeniem. [1][6]

Dla żądań wymagających preflight sytuacja jest inna. Przeglądarka wysyła najpierw `OPTIONS`. Jeżeli odpowiedź preflight nie spełnia wymagań CORS, właściwe żądanie nie jest wysyłane. [2][5][6]

To rozróżnienie jest ważne przy debugowaniu operacji zmieniających dane. Nie wolno zakładać, że komunikat CORS oznacza, iż backend na pewno nie otrzymał żadnego żądania.

Proste żądania: kiedy przeglądarka pomija preflight

MetodaPrzykładowe nagłówkiProste żądanieEfekt
GETAcceptTakBez preflight
POSTContent-Type: text/plainTakBez preflight
POSTContent-Type: application/jsonNieJSON powoduje preflight
PUTContent-Type: application/jsonNieMetoda powoduje preflight
GETAuthorization: Bearer ...NieNagłówek powoduje preflight

Żądanie jest traktowane jako proste tylko wtedy, gdy spełnia wszystkie warunki bezpiecznej listy CORS. Dozwolone metody to `GET`, `HEAD` i `POST`. Ręcznie ustawiane nagłówki muszą należeć do bezpiecznej listy, a `Content-Type`, jeśli jest ustawiany, może używać tylko `application/x-www-form-urlencoded`, `multipart/form-data` albo `text/plain`. [1]

Typowe `POST` z `Content-Type: application/json` nie jest prostym żądaniem i zwykle uruchamia preflight. Podobnie `PUT`, `DELETE` albo niestandardowy nagłówek, taki jak `X-Request-ID`, powodują konieczność sprawdzenia uprawnień przed właściwym żądaniem. [1][11]

Preflight OPTIONS: co sprawdza przeglądarka

Preflight jest automatycznym żądaniem `OPTIONS`. Przeglądarka dodaje `Origin`, `Access-Control-Request-Method` i, jeśli trzeba, `Access-Control-Request-Headers`. Serwer odpowiada informacją, jakie originy, metody i nagłówki dopuszcza. [2][5]

Jeśli aplikacja chce wysłać `DELETE` z nagłówkiem `Authorization`, przeglądarka może najpierw zapytać serwer, czy dany origin może wykonać `DELETE` i użyć tego nagłówka. Dopiero pozytywna odpowiedź umożliwia wysłanie właściwego żądania. [5]

Preflight tworzy dodatkowy cykl sieciowy, dlatego jego wynik może być przechowywany w specjalnej pamięci podręcznej CORS niezależnej od zwykłej pamięci podręcznej HTTP. [5][10]

Najważniejsze nagłówki CORS

NagłówekKierunekRola
OriginŻądanieOrigin inicjujący żądanie
Access-Control-Allow-OriginOdpowiedźOrigin, któremu przeglądarka może udostępnić odpowiedź
Access-Control-Allow-MethodsOdpowiedź preflightMetody dopuszczone przez preflight
Access-Control-Allow-HeadersOdpowiedź preflightNagłówki żądania dopuszczone przez preflight
Access-Control-Allow-CredentialsOdpowiedźZgoda na udostępnienie odpowiedzi z danymi uwierzytelniającymi
Access-Control-Expose-HeadersOdpowiedźDodatkowe nagłówki odpowiedzi dostępne dla JavaScriptu
Access-Control-Max-AgeOdpowiedź preflightCzas przechowywania wyniku preflight
Vary: OriginOdpowiedźInformacja dla pamięci podręcznej, że odpowiedź zależy od Origin

`Origin` jest nagłówkiem żądania wskazującym origin inicjujący żądanie. `Access-Control-Allow-Origin` jest nagłówkiem odpowiedzi serwera i mówi przeglądarce, czy odpowiedź może zostać udostępniona kodowi z danego originu. [1][2][8]

`Access-Control-Allow-Methods` i `Access-Control-Allow-Headers` są szczególnie istotne dla preflight. `Access-Control-Allow-Credentials` pozwala udostępnić odpowiedź żądania wykonywanego z danymi uwierzytelniającymi, natomiast `Access-Control-Expose-Headers` może odsłonić JavaScriptowi dodatkowe nagłówki odpowiedzi poza bezpieczną listą. [1][2]

Jeżeli serwer dynamicznie zwraca różne wartości `Access-Control-Allow-Origin` zależnie od żądania, MDN zaleca również `Vary: Origin`, aby pamięć podręczna wiedziała, że odpowiedź zależy od originu. [1]

`Access-Control-Allow-Origin`: `*` czy konkretny origin

`Access-Control-Allow-Origin: *` oznacza, że odpowiedź może zostać udostępniona dowolnemu originowi dla żądań bez danych uwierzytelniających. Dla publicznego API może to być prawidłowa konfiguracja. [2][8]

Dla prywatnych lub wrażliwych punktów końcowych API lepiej dopuszczać minimalny wymagany zestaw originów. MDN i OWASP rekomendują konfigurację możliwie restrykcyjną zamiast bezwarunkowego `*`. [14][17]

Jeżeli aplikacja obsługuje wiele dozwolonych originów, serwer powinien porównać `Origin` z listą dozwolonych originów i zwrócić konkretną zaakceptowaną wartość. Nie należy po prostu odbijać dowolnego `Origin` przesłanego przez klienta. [8][16]

Cookies i dane uwierzytelniające: najczęstsze źródło problemów

W Fetch API można użyć `credentials: "include"`, aby poprosić przeglądarkę o wysyłanie danych uwierzytelniających także w żądaniach cross-origin. Sam parametr po stronie frontendu nie wystarcza. Serwer musi odpowiedzieć między innymi `Access-Control-Allow-Credentials: true` oraz konkretnym `Access-Control-Allow-Origin`. Symbol `*` nie jest wtedy dozwolony. [2][6][9]

Cookies podlegają dodatkowo własnym regułom. `SameSite` może spowodować, że cookie w ogóle nie zostanie wysłane w żądaniu cross-site. Dla `SameSite=None` wymagany jest również `Secure`. [13]

CORS i `SameSite` rozwiązują inne problemy. Można mieć poprawny CORS, ale brak cookie przez `SameSite`, albo poprawnie wysyłane cookie i blokadę odczytu odpowiedzi przez błędny CORS.

Dlaczego `mode: "no-cors"` zwykle niczego nie naprawia

`fetch(..., { mode: "no-cors" })` nie jest sposobem na obejście CORS dla aplikacji, która musi przeczytać odpowiedź API. Takie żądanie ma dodatkowe ograniczenia metod i nagłówków, a odpowiedź jest typu opaque. JavaScript nie może odczytać jej treści ani nagłówków, a kod stanu widoczny przez Fetch API ma wartość `0`. [6][7][11]

`no-cors` ma zastosowania specjalistyczne, między innymi w niektórych scenariuszach Service Worker, ale dla typowego `fetch` pobierającego JSON jest zwykle błędnym rozwiązaniem. [6]

Jeżeli nie kontrolujesz zewnętrznego API i serwer nie udostępnia go przez CORS, rozwiązaniem może być backend lub proxy kontrolowane przez Ciebie, które wykona żądanie serwer-serwer. [11][12]

Dlaczego curl, Postman albo backend działają, a przeglądarka nie

Polityka tego samego źródła i egzekwowanie CORS są mechanizmami bezpieczeństwa przeglądarki. Klient HTTP działający poza tym modelem może wysłać żądanie bez podlegania przeglądarkowej kontroli CORS. Z tego powodu punkt końcowy API może działać w curl lub narzędziu API, a jednocześnie być niedostępny dla JavaScriptu uruchomionego na konkretnej stronie. [2][3][16]

To również pokazuje, dlaczego CORS nie jest uwierzytelnianiem API. Klient spoza przeglądarki może ustawić własny `Origin`, dlatego OWASP ostrzega, aby nie używać tego nagłówka jako dowodu tożsamości klienta. [16]

API nadal potrzebuje normalnych mechanizmów autoryzacji, tokenów, sesji, kontroli uprawnień i walidacji po stronie serwera.

CORS nie zastępuje CSRF ani autoryzacji

Polityka tego samego źródła przede wszystkim ogranicza odczyt danych między originami. Nie oznacza to, że każde żądanie zmieniające stan z innej strony jest automatycznie niemożliwe. Proste formularze i niektóre proste żądania mogą być wysyłane cross-origin, dlatego aplikacja oparta na cookies nadal potrzebuje ochrony CSRF. [3][15]

OWASP wprost zaleca, aby nie polegać wyłącznie na CORS i `Origin` jako kontroli dostępu do wrażliwych danych. Wrażliwe punkty końcowe API wymagają uwierzytelniania i autoryzacji niezależnie od CORS. [16]

CORS odpowiada na pytanie, czy przeglądarka może udostępnić odpowiedź kodowi danego originu. Nie odpowiada na pytanie, czy użytkownik albo klient ma prawo wykonać daną operację.

Najczęstsze błędy i jak je diagnozować

Brak `Access-Control-Allow-Origin` jest jednym z najczęstszych błędów. Inne typowe problemy to niedozwolona metoda, brak wymaganego nagłówka w `Access-Control-Allow-Headers`, użycie `*` z danymi uwierzytelniającymi, błędna odpowiedź na OPTIONS albo przekierowanie podczas przepływu CORS. [11][12]

JavaScript celowo dostaje ograniczoną informację o przyczynie błędu. Dokładniejszy powód należy sprawdzić w konsoli i zakładce Network w DevTools. [1][11]

Komunikat `CORS request did not succeed` nie musi oznaczać błędnej konfiguracji CORS. MDN wskazuje, że może wynikać z DNS, przekroczenia czasu, odmowy połączenia, TLS, mixed content albo blokady przez rozszerzenie. [11]

  • Sprawdź dokładny origin frontendu: schemat, host i port.
  • Sprawdź żądanie `OPTIONS`, jeśli występuje.
  • Zweryfikuj kod stanu odpowiedzi preflight i wszystkie `Access-Control-Allow-*`.
  • Sprawdź, czy właściwa odpowiedź także zawiera `Access-Control-Allow-Origin`.
  • Przy cookies zweryfikuj `credentials`, `Access-Control-Allow-Credentials`, `SameSite` i `Secure`.
  • Sprawdź przekierowania, TLS, mixed content i błędy sieciowe.
  • Porównaj żądanie z przeglądarki z działającym żądaniem z curl lub narzędzia API.

Niebezpieczne konfiguracje CORS

Bezwarunkowe odbijanie wartości `Origin` jest niebezpieczne, jeżeli żądania z danymi uwierzytelniającymi zwracają wrażliwe dane. Atakujący może uruchomić własną stronę i spróbować odczytać odpowiedź w kontekście zalogowanego użytkownika, jeżeli jego origin zostanie zaakceptowany. [16]

Podobnie ryzykowne są zbyt szerokie wyrażenia regularne i zaufanie wszystkim subdomenom, jeżeli część z nich może zostać przejęta lub kontrolowana przez inny zespół. OWASP rekomenduje dokładne dopasowanie do listy zaufanych originów. [16][17]

`Access-Control-Allow-Origin: *` nie jest samo w sobie podatnością dla całkowicie publicznych danych bez danych uwierzytelniających. Problem powstaje wtedy, gdy polityka udostępnia więcej danych lub originów niż rzeczywiście powinna. [8][17]

Wydajność: koszt preflight i `Access-Control-Max-Age`

Preflight oznacza dodatkowe żądanie `OPTIONS` przed właściwym żądaniem, więc może zwiększyć opóźnienie. Wynik może być przechowywany za pomocą `Access-Control-Max-Age`. Pamięć preflight jest oddzielna od zwykłej pamięci podręcznej HTTP. [5][10]

MDN podaje, że domyślna wartość bez nagłówka wynosi 5 sekund. Przeglądarki mogą narzucać własne górne limity, nawet jeżeli serwer poda większą wartość. Firefox ogranicza ją do 86400 sekund, a Chromium od wersji 76 do 7200 sekund. [10]

Warto przechowywać stabilne polityki preflight, ale nie kosztem możliwości szybkiego wycofania błędnej lub zbyt szerokiej polityki bezpieczeństwa.

Praktyczna konfiguracja i checklista wdrożeniowa

Najpierw zdecyduj, czy punkt końcowy API w ogóle ma być dostępny cross-origin. Jeśli nie, nie dodawaj nagłówków CORS. Jeśli tak, zdefiniuj dokładną listę dozwolonych originów, potrzebne metody i nagłówki oraz zdecyduj, czy żądania wymagają danych uwierzytelniających. [14][17]

Dla dynamicznej listy dozwolonych originów zwracaj tylko zweryfikowany origin i dodaj `Vary: Origin`. Dla danych uwierzytelniających zwracaj konkretny origin oraz `Access-Control-Allow-Credentials: true`. Nie próbuj naprawiać problemu przez dodawanie nagłówków CORS po stronie frontendu. [1][2]

Po wdrożeniu przetestuj zarówno proste żądanie, jak i preflight, scenariusz z cookies, niedozwolony origin oraz rzeczywiste błędy w DevTools.

  • Czy cross-origin jest naprawdę potrzebny?
  • Jakie dokładnie originy mają być dozwolone?
  • Czy potrzebne są dane uwierzytelniające?
  • Jakie metody i nagłówki żądania są wymagane?
  • Czy `OPTIONS` dociera do aplikacji i zwraca poprawną odpowiedź?
  • Czy właściwa odpowiedź ma poprawne nagłówki CORS?
  • Czy przy dynamicznym originie ustawiasz `Vary: Origin`?
  • Czy punkt końcowy API nadal wymaga normalnego uwierzytelniania i autoryzacji?
  • Czy polityka została przetestowana dla originu dozwolonego i niedozwolonego?

CORS należy traktować jako regułę dostępu do odpowiedzi w przeglądarce, a nie jako system ochrony API przed wszystkimi klientami. Najpierw trzeba ustalić origin frontendu i API, potem sprawdzić, czy żądanie jest proste czy wymaga preflight, a następnie zweryfikować odpowiedź OPTIONS i nagłówki właściwej odpowiedzi. Bezpieczna konfiguracja powinna dopuszczać tylko rzeczywiście potrzebne originy, metody i nagłówki oraz pozostawiać uwierzytelnianie i autoryzację w warstwie aplikacji.

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

Najczęściej zadawane pytania

Co to jest CORS?

CORS to protokół HTTP używany przez przeglądarki do kontrolowanego udostępniania odpowiedzi między różnymi originami. Serwer za pomocą nagłówków określa, które originy mogą odczytać odpowiedź. [1][2]

Dlaczego API działa w Postmanie, ale nie w przeglądarce?

Ponieważ CORS jest egzekwowany przez przeglądarkę w ramach polityki tego samego źródła. Narzędzia HTTP poza przeglądarką nie podlegają temu samemu mechanizmowi. [2][3][16]

Czy CORS blokuje wysłanie żądania?

Nie zawsze. Proste żądanie może zostać wysłane, a przeglądarka może jedynie zablokować JavaScriptowi dostęp do odpowiedzi. Przy nieudanym preflight właściwe żądanie nie zostanie wysłane. [1][5][6]

Co wywołuje preflight?

Między innymi metody spoza GET, HEAD, POST, niestandardowe nagłówki oraz Content-Type: application/json. Przeglądarka wysyła wtedy OPTIONS. [1][5]

Czy Access-Control-Allow-Origin dodaje się w frontendzie?

Nie. To nagłówek odpowiedzi serwera. Frontend nie może naprawić brakującej zgody serwera przez ustawienie go w fetch. [1][8][12]

Czy mode: "no-cors" naprawia problem?

Nie dla typowego API, z którego trzeba odczytać dane. Odpowiedź jest wtedy opaque i JavaScript nie może odczytać treści, nagłówków ani normalnego kodu stanu. [6][7]

Czy można używać Access-Control-Allow-Origin: *?

Tak dla publicznych odpowiedzi bez danych uwierzytelniających. Nie można używać * do udostępniania odpowiedzi żądań z danymi uwierzytelniającymi. [2][8][9]

Jak działa CORS z cookies?

Frontend zwykle używa credentials: "include", a serwer musi zwrócić konkretny origin i Access-Control-Allow-Credentials: true. Cookie nadal podlega regułom SameSite i Secure. [6][9][13]

Czy CORS chroni API przed curl albo botem?

Nie. CORS ogranicza przeglądarkowy dostęp JavaScriptu do odpowiedzi. Klient spoza przeglądarki może wysłać własne żądanie i własny Origin, dlatego API potrzebuje autoryzacji niezależnie od CORS. [16]

Czy CORS chroni przed CSRF?

Nie w pełni. Aplikacje korzystające z cookies nadal potrzebują ochrony CSRF. Polityka tego samego źródła i CORS nie zastępują tokenów CSRF ani innych zalecanych mechanizmów. [3][15][16]

Po co Vary: Origin?

Gdy serwer dynamicznie zwraca Access-Control-Allow-Origin zależnie od żądania, Vary: Origin informuje pamięć podręczną, że odpowiedź może różnić się między originami. [1]

Jak ograniczyć liczbę preflightów?

Można przechowywać wynik preflight przez Access-Control-Max-Age albo, jeśli ma to sens funkcjonalny i bezpieczeństwa, używać żądań spełniających warunki prostego CORS. Przeglądarki stosują własne limity pamięci podręcznej. [1][10][11]

Źródła i przypisy

  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

Czy ten artykuł był pomocny?

Nowe artykuły na e-mail

Jeden krótki e-mail przy każdym nowym artykule. Bez spamu, wypisujesz się jednym kliknięciem.

Wykorzystujemy e-mail wyłącznie do wysyłki nowych artykułów. Bez udostępniania stronom trzecim.

Wróć do bazy wiedzy