CORS (Cross-Origin Resource Sharing) to mechanizm oparty na nagłówkach HTTP, który pozwala przeglądarce bezpiecznie komunikować się z serwerem o innym originie — mimo że domyślnie blokuje to SOP (Same-Origin Policy). Najczęstszy błąd, „No 'Access-Control-Allow-Origin' header is present”, oznacza, że serwer nie wysłał nagłówka zezwalającego na taki kontakt. W nauce można to obejść tymczasowym proxy, ale docelowo trzeba poprawnie skonfigurować nagłówki Access-Control-* po stronie backendu.
Czym jest SOP (Same-Origin Policy)?
Czym w uproszczeniu jest SOP?
Polskie tłumaczenie same-origin policy może brzmieć: reguła tego samego pochodzenia. W uproszczeniu SOP to mechanizm bezpieczeństwa zmniejszający ryzyko ataków, który reguluje komunikację między dwiema stronami internetowymi. Do takiej komunikacji może dojść tylko wtedy, kiedy strony mają ten sam origin, czyli (znów w uproszczeniu) zbieżny adres. Przykładowo strona http://www.devmentor.pl/assets/images skomunikuje się ze stroną http://www.devmentor.pl/subpage/blog.html.
Ale z powodu SOP komunikacja z poniższym adresem zostanie zablokowana (brakuje „jedynie" www): http://devmentor.pl/subpage/blog.html
Na Wikipedii znajdziesz więcej przykładów możliwych różnic między originami.
Czym jest origin i jak brzmi definicja SOP?
To teraz koniec uproszczeń. Oto definicja SOP z MDN:
Same-origin policy (reguła tego samego pochodzenia) to istotny mechanizm bezpieczeństwa, który określa sposób, w jaki dokument lub skrypt jednego pochodzenia (origin) może komunikować się z zasobem innego pochodzenia. Pozwala to na odizolowanie potencjalnie szkodliwych dokumentów i tym samym redukowane są czynniki sprzyjające atakom.
Origin to trzy nierozłączne elementy:
- Protokół (schemat) – np. http i https
- Host – np. devmentor.pl czy subdomena.devmentor.pl (to dwa różne hosty)
- Port – jeśli nie jest określony, to jego domyślną wartością jest 80.
Jeżeli którykolwiek z powyższych elementów w originach obu komunikujących się źródeł jest inny, komunikacja zakończy się niepowodzeniem. W tym miejscu być może pomyślałeś, że to dziwne, ponieważ zdarzało Ci się już łączyć z API, które przecież mieściło się pod zupełnie innym adresem niż np. Twój http://localhost:5500/. I słusznie – tego typu komunikacja rzeczywiście jest umożliwiana. W przeciwnym razie duża część aplikacji internetowych w ogóle by nie działała. Więc jak to w końcu jest? Z pomocą przychodzi nam CORS, z którego dobrodziejstw nieświadomie korzystamy na co dzień.
Czym jest CORS (Cross-Origin Resource Sharing)?
Chciałbym zwrócić Twoją uwagę na fragment rozwinięcia skrótu: cross-origin. Od razu wskazuje on na to, że komunikacja (resource sharing) będzie zachodzić między skryptami/dokumentami o różnych originach. CORS to mechanizm bazujący na nagłówkach HTTP regulujący zasady tej komunikacji — inaczej mówiąc, polityka CORS to zestaw reguł, które serwer przesyła przeglądarce w nagłówkach odpowiedzi.
Właśnie w nagłówkach przeglądarka przesyła do odpytywanego serwera informacje, które pozwolą mu „zdecydować", czy ufa on stronie odpytującej (choćby naszemu http://localhost:5500/). Serwer także przesyła informacje do przeglądarki, m.in. z „prośbą" o zezwolenie na kontakt z jego originem. W nagłówkach przy tzw. prostym zapytaniu (ang. simple request) znajdzie się więc metoda HTTP (jedna z trzech: GET, HEAD lub POST) oraz – oprócz nagłówków automatycznie ustawianych przez przeglądarkę – tzw. CORS-safelisted request-header. Jeśli korzystałeś już z API, to kojarzysz zapewne np. nagłówek Content-Type.
Abyśmy mogli dostać się do zasobów danego serwera, ten, kto wystawia API, musi zadbać o odpowiednie ustawienia („pozwolenia") – nagłówki z serii Access-Control-…-…. Po stronie back endu ten fragment kodu mógłby wyglądać np. tak:
async function handler(req, res) {
res.setHeader("Access-Control-Request-Method", "GET")
res.setHeader("Access-Control-Allow-Origin", "https://devmentor.pl")
res.setHeader("Access-Control-Allow-Headers", "Content-Type")
// [...]
}
Oczywiście szczegóły dotyczące wymaganych nagłówków powinniśmy znaleźć w dokumentacji. Jeżeli chcesz obejrzeć wideo na temat działania CORS i nagłówków, to zapraszam Cię na kanał Artura Chmary do nagrania CORS w pigułce: działanie i naprawa. Chciałbym tylko uczulić Cię na jeden mały błąd: w filmie CORS przedstawiono jako „blokadę" komunikacji cross-origin, gdy w rzeczywistości CORS tę komunikację umożliwia (to SOP ją blokuje). Łatwo o taką pomyłkę z tej racji, że CORS często jest powodem błędów – mamy więc wrażenie, że utrudnia nam komunikację z API ;)
Czym różni się CORS od SOP?
| Cecha | SOP (Same-Origin Policy) | CORS (Cross-Origin Resource Sharing) |
|---|---|---|
| Co robi | Domyślnie blokuje komunikację między różnymi originami | Otwiera tę komunikację tam, gdzie serwer na to pozwoli |
| Gdzie działa | Wbudowany mechanizm przeglądarki, zawsze aktywny | Zestaw nagłówków HTTP, które musi ustawić serwer |
| Kto go konfiguruje | Nikt — to domyślne zachowanie przeglądarki | Backend/administrator API (nagłówki Access-Control-*) |
| Efekt braku konfiguracji | Blokada — tak działa z założenia | Błąd w konsoli, np. „No 'Access-Control-Allow-Origin' header…" |
Jak podkreśla Mateusz, mentor Full Stack w devmentor.pl, na mentoringu ten sam błąd — mylenie SOP z CORS — pojawia się u 90% kursantów przy pierwszym kontakcie z zewnętrznym API. Najszybciej rozwiewa go prosta analogia: SOP to domyślnie zamknięte drzwi budynku („nikogo nie wpuszczamy, chyba że jesteś stąd"), a CORS to lista gości, którą właściciel budynku — czyli serwer — sam ustala i przekazuje ochronie, czyli przeglądarce. To serwer decyduje, kogo mimo wszystko wpuścić, a nie przeglądarka.
Jakie nagłówki HTTP odpowiadają za CORS?
To właśnie rodzina nagłówków Access-Control-* decyduje, czy i na jakich warunkach przeglądarka wpuści odpowiedź z innego originu. Serwer wysyła je w odpowiedzi (czasem też w odpowiedzi na wspomniany wcześniej preflight request), a przeglądarka je odczytuje i egzekwuje.
| Nagłówek | Do czego służy | Przykładowa wartość |
|---|---|---|
Access-Control-Allow-Origin | Określa, które originy mogą odczytać odpowiedź serwera | https://devmentor.pl lub * (dowolny origin) |
Access-Control-Allow-Methods | Lista metod HTTP dozwolonych w zapytaniach cross-origin | GET, POST, PUT |
Access-Control-Allow-Headers | Lista nagłówków żądania, które serwer akceptuje | Content-Type, Authorization |
Access-Control-Allow-Credentials | Czy przeglądarka może wysyłać cookies i dane uwierzytelniające | true |
Access-Control-Max-Age | Jak długo (w sekundach) przeglądarka może cache'ować wynik preflight, by nie powtarzać go przy każdym zapytaniu | 86400 |
Wartość * w Access-Control-Allow-Origin jest wygodna przy publicznych, niewymagających uwierzytelnienia API, ale nie zadziała razem z Access-Control-Allow-Credentials: true — przeglądarka wymaga wtedy konkretnego originu, a nie „gwiazdki". To jeden z częstszych powodów, dla których nagłówki wydają się skonfigurowane poprawnie, a błąd CORS i tak się pojawia.
Z doświadczenia mentorów devmentor.pl wynika, że błędy związane z nagłówkami
Access-Control-*to jeden z dziesięciu najczęściej zgłaszanych problemów na sesjach mentoringowych dotyczących pracy z API — wyprzedza je jedynie problem z limitem zapytań, opisany w artykule Limity API w nauce programowania – obejdź to!.
Jakie błędy związane z CORS możesz napotkać?
Być może właśnie z ich powodu tutaj jesteś. Skąd biorą się błędy związane z CORS? Podczas nauki programowania możesz napotkać co najmniej dwie sytuacje:
- Próbujesz skontaktować się ze starszym API, które nie ma zdefiniowanego CORS-a.
- Podczas używania JSON Servera schemat (protokół), z którego korzystasz, różni się od schematu adresu, pod którym mieszczą się pożądane zasoby.
Dlaczego pojawia się błąd „No Access-Control-Allow-Origin header present"?
Gdy próbujemy skontaktować się z API, które nie ma zdefiniowanego CORS-a (czyli np. dawno nie było aktualizowane), to napotkamy błąd: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Na czym polega ten błąd? Na poziomie przeglądarki i serwera, z którym próbujemy się skomunikować, dochodzi do wymiany informacji:
- Zanim przeglądarka wyśle do serwera zapytanie o zasoby, najpierw wykonuje tzw. preflight request – zapytanie o wytyczne, jakie przeglądarka powinna spełnić, by móc następnie wykonać bezpieczne zapytanie o zasoby.
- Serwer wysyła wytyczne, m.in. origin, na kontakt z którym przeglądarka powinna zezwolić (przeglądarka oczekuje, że otrzyma tę informację). Jest to nasz Access-Control-Allow-Origin header, którego brak powoduje błąd.
Czy to oznacza, że API bez CORS jest zupełnie bezużyteczne? Nie, jesteśmy w stanie obejść to za pomocą tzw. pośrednika (proxy). Ale uwaga: to rozwiązanie nie nadaje się na produkcję, służy jedynie nauce, testom i tworzeniu demo projektów np. do portfolio. W „prawdziwym" projekcie po prostu nie będziemy korzystać z takiego API, które nie spełnia wymogów bezpieczeństwa.
Jak działa proxy (pośrednik) przy błędach CORS?
Najpierw przyjrzyjmy się poniższemu URL: https://cors-anywhere.herokuapp.com/http://api.weatherapi.com/v1/future.json
Jego pierwsza część, to pośrednik (proxy): https://cors-anywhere.herokuapp.com/, a druga to nasze zapytanie do API pogodowego: http://api.weatherapi.com/v1/future.json
Jak to działa? Za chwilę powiem o tym, jak można stworzyć własne proxy, ale najpierw dowiedzmy się, jaki mechanizm tym rządzi.
- Proxy poprzedza nasze zapytanie do API.
- Otrzymuje odpowiedź z API (w tym miejscu bez proxy zobaczylibyśmy błąd No 'Access-Control-Allow-Origin' header…).
- Proxy do odpowiedzi z API dodaje nagłówek Access-Control-Allow-Origin.
- Przeglądarka nie zgłasza błędu z CORS, ponieważ otrzymuje nagłówek, którego oczekiwała, wysyła więc już do API właściwe zapytanie o zasoby.
Mateusz, mentor Full Stack w devmentor.pl, zauważa, że proxy typu cors-anywhere bywa zgłaszane jako „gotowe rozwiązanie" na rozmowach rekrutacyjnych — a to jeden z sygnałów, po których rekruter lub mentor techniczny od razu wie, że kandydat nie rozumie, dlaczego to backend, a nie przeglądarka, powinien odpowiadać za nagłówki CORS.
Jak stworzyć własne proxy z cors-anywhere?
Własne proxy możemy stworzyć dzięki umieszczeniu kodu dodającego odpowiednie nagłówki i opcje na darmowym serwisie do hostowania stron internetowych, np. Heroku. Skąd wziąć taki kod? Gotowe rozwiązanie znajdziesz na GitHubie: cors-anywhere. Jego instalację i deploy na Heroku w kilku prostych krokach przedstawia odpowiedź na Stack Overflow. Przed stworzeniem własnego proxy możemy przetestować działanie pośrednika dzięki demo cors-anywhere. Jako demo ma ono limity odpytań, lecz wystarczy do zapoznania się z tematem.
Dlaczego pojawia się błąd „Cross origin requests are only supported for protocol schemes"?
Pełna treść błędu może brzmieć: XMLHttpRequest cannot load localhost:4000/data.json. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.
Teoretycznie przy korzystaniu z lokalnego serwera, gdzie mamy różnicę portów (np. JSON Server otwiera się na localhost:3000, a webpack DevServer na localhost:8080), powinniśmy napotkać problem z CORS. Zwykle tak się jednak nie dzieje, ponieważ narzędzia pokroju JSON Servera zezwalają na komunikację z localhostem (po odpytaniu API w konsoli w przeglądarce w zakładce Network znajdziesz informacje o nagłówkach, w tym także nagłówku Access-Control-Allow-Origin).
Powyższy błąd wystąpi jednak, gdy uruchomisz pliki przez protokół file:// – czyli gdy np. otworzysz w przeglądarce plik bezpośrednio z katalogu. Wówczas adres będzie wyglądał podobnie do tego: file:///C:/Users/mateusz/Desktop/my-website/index.html
Treść błędu w tym przypadku może brzmieć również mniej więcej tak: Origin null is not allowed by Access-Control-Allow-Origin.
Lokalny system plików nie posiada originu (protokołu, hosta, portu), dlatego jest blokowany przez SOP. Swego czasu Firefox umożliwiał komunikację między tymi samymi katalogami i podkatalogami, lecz ze względów bezpieczeństwa zostało to wycofane.
Jak sobie z tym poradzić? Wystarczy użyć np. rozszerzenia do VS Code: Live Server. Pozwoli on otworzyć nasze pliki przez protokół http://. Adres w pasku przeglądarki zmieni się na podobny do tego: http://127.0.0.1:5500/index.html. Błąd nie powinien już występować.
Gdzie znaleźć pełną listę błędów CORS?
Na stronie MDN znajdziesz listę błędów związanych z CORS wraz z krótkimi wskazówkami co do ich przyczyny. Być może pomogą Ci one w rozwiązaniu problemu lub przynajmniej w sformułowaniu odpowiedniego zapytania w Google.
Błędy związane z CORS potrafią być frustrujące – wystarczy rzucić okiem na fora czy zakładki Issues popularnych narzędzi na GitHubie. Zapewne więc przyjdzie Ci nie raz się z nimi mierzyć. Dlatego już na początku warto zrozumieć temat komunikacji między przeglądarką a serwerem, znać nagłówki, które za nią odpowiadają, a na potrzeby nauki programowania wiedzieć o istnieniu proxy.


