Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!

CORS, SOP, origin, nagłówki HTTP i błędy z nimi związane

Zrozum temat, by lepiej radzić sobie z mechanizmami CORS

No Access-Control-Allow-Origin header present – i już się w nas gotuje! Czym jest ten denerwujący CORS, dlaczego sprawia takie problemy i „utrudnia” pracę z API? A może to nie on utrudnia, lecz SOP? Robi się ciekawie. Dowiedz się więcej o komunikacji między originami i nie daj się pokonać błędom!

Spis treści

 SOP (Same-Origin Policy) – czyli początek wszystkiego

 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

https://www.devmentor.pl/assets/images

skomunikuje się ze stroną

https://www.devmentor.pl/subpage/blog.html

Ale z powodu SOP komunikacja z poniższym adresem zostanie zablokowana (brakuje „jedynie” www):

https://devmentor.pl/subpage/blog.html

Na Wikipedii znajdziesz więcej przykładów możliwych różnic między originami.

 Definicja SOP i znaczenie originu

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ń.

 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. 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:

port default 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 😉

 Błędy związane z CORS

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:

  1. Próbujesz skontaktować się ze starszym API, które nie ma zdefiniowanego CORS-a.
  2. 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.

 Ad 1: 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:

  1. 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.
  2. 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.

Działanie pośrednika (proxy)

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.

  1. Proxy poprzedza nasze zapytanie do API.
  2. Otrzymuje odpowiedź z API (w tym miejscu bez proxy zobaczylibyśmy błąd No 'Access-Control-Allow-Origin' header…).
  3. Proxy do odpowiedzi z API dodaje nagłówek Access-Control-Allow-Origin.
  4. 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.

Własny pośrednik – 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.

 Ad 2: XMLHttpRequest; 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ć.

 Spis błędów związanych z 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, a na potrzeby nauki programowania wiedzieć o istnieniu proxy.

Udostępnij ten artykuł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.