Błąd "Same Origin Policy"

0

Cześć,

mam bardzo dziwną sytuację.

  • Postawiłem frontend, backend i bazę danych za pomocą docker-compose
  • Gdy odpytuję backend za pomocą postmana - wszystko gra.
  • Gdy odpytuję backend z frontendowego kontenera używając curl - wszystko gra
  • Gdy odpalam frontend i backend poza kontenerami - wszystko się ładnie łączy.

Ale:

  • Gdy używam fetch() na frontendzie pojawia się problem. Adres jest taki sam jak gdy używam curl. Poza kontenerem działa to dobrze.

Mam podejrzenie że jest to związane z błędną konfiguracją nginx którego używam do hostowania frontendu w kontenerze.
Tu macie konfigurację:

worker_processes  1;

events {
  worker_connections  1024;
}

http {
  server {
    listen 3000;
    server_name  frontend;

    root   /usr/share/nginx/html;
    index  index.html index.htm;
    include /etc/nginx/mime.types;

    gzip on;
    gzip_min_length 1000;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    location / {
      try_files $uri $uri/ /index.html;
    }
  }
}

Czy macie pomysł co może być nie tak?

1
adams0 napisał(a):

Gdy używam fetch na frontendzie pojawia się problem.

Nie napisałeś dokładnie co to za problem :P

Jeśli frontend i backend działa na osobnych portach to może być kłopot z CORS

0

To jest problem z CORS.

Zablokowano żądanie do zasobu innego pochodzenia: zasady „Same Origin Policy” nie pozwalają wczytywać zdalnych zasobów z „http://backend:3001/translation/black” (nieudane żądanie CORS). Kod stanu: (null).

TypeError: NetworkError when attempting to fetch resource.

Tylko że backend nie odpowiada w ogóle na to zapytanie.

Mam CORSY obsłużone dobrze na backendzie.
Gdy odpalam bez kontenerów to też hostuję frontend i backend lokalnie na osobnych portach i wszystko działa.

0

@adams0: podszedłbym do tego tak:

  1. Sprawdź headery jakie lecą w preflight request OPTIONS w konsoli dewelopera w przeglądarce i zobacz co leci nagłówkach Origin, allows-headers, allows-method etc.
  2. Skopiuj nagłówki do postmana czy innego curla i zobacz co poleci Ci dla takiego requestu. Backend musi zwrócić Ci, że dany Origin (URL apki frontendowej) może czytać zasoby.

Napisz z jakiego originu wykonujesz request w przeglądarce do jakiego originu na backendzie. Może kontener znajduje się w jakieś innej sieci, albo coś.

2
adams0 napisał(a):

To jest problem z CORS.

Zablokowano żądanie do zasobu innego pochodzenia: zasady „Same Origin Policy” nie pozwalają wczytywać zdalnych zasobów z „http://backend:3001/translation/black” (nieudane żądanie CORS). Kod stanu: (null).

TypeError: NetworkError when attempting to fetch resource.

Tylko że backend nie odpowiada w ogóle na to zapytanie.

Mam CORSY obsłużone dobrze na backendzie.
Gdy odpalam bez kontenerów to też hostuję frontend i backend lokalnie na osobnych portach i wszystko działa.

Pewnie serwujesz aplikację z jakiegoś http://frontend:3002/ a strzelasz pod http://backend:3001 i to jest domyślnie niedozwolone przez CORS (chyba że to ustawisz, czego nie powinieneś robić).

To co powinieneś zrobić, moim zdaniem to:

  • Albo podczas developmentu serwować front z tego samego origina co backend (czyli coś w stylu front z localhost:3000 i backend localhost:3000), mógłbyś to ustawić w nginxie.
  • Albo podczas developmentu ustawić customowe-CORS, które potem musiałbyś wyłączyć na prodzie, i serwować aplikację i tak z jednego origina.
  • Albo wystawić w kontenerze z frontem server HTTP, niech front strzela do tego servera w kontenerze frontowym, a server w konenterze frontowym przekierowuje zapytania do servera backendowego; ale to też jest mega na około.

Moim zdaniem lepsze będzie to pierwsze.

adams0 napisał(a):

Cześć,

mam bardzo dziwną sytuację.

Nie jest ani trochę dziwny - to wręcz książkowy błąd z CORS.

Ty to widzisz jako nieudane połącznie i dlatego dziwisz się czemu wszystkie narzędzia (curl etc.) działają, a fetch() nie.

Ale w rzeczywistości połączenie się udaje i jest nawiązywane, jedynie odpowiedź nie jest przekazywana z przeglądarki do runtime'u JS'a z uwagi ustawienia zabezpieczeń, na co JS reaguje wyjątkiem - co Ty błędnie interpretujesz jako błąd gdzieś na sieci.

0

Dotychczas obsługiwałem to takim wpisem w expresie:

server.use((req, res, next) => {
    res.append('Access-Control-Allow-Origin', ['*']);
    res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.append('Access-Control-Allow-Headers', 'Content-Type');
    next();
});

Gdzieś przeczytałem że frontend i backend powinny być na osobnych kontenerach.
@Riddle: Prawie zgadłeś. Serwuję z 0.0.0.0:3000 a backend słucha na 0.0.0.0:3001
Są w ramach jednej sieci.

Chciałbym to zrobić zgodnie z jakimiś dobrymi praktykami.
Czy to oznacza że muszę mieć jeden kontener z frontendem i backendem?

Dwa pierwsze wyszukiwania w googlu sugerują użycie innego portu dla backendu i frontendu:

Czy przy dwóch kontenerach będę musiał wtedy obsłużyć jakiś url na backendzie i obsłużyć go jako błąd na frontendzie (jak ten sam port to pewnie poleci do obu) ?

Wiem że to są bardzo naiwne pytania ale nie wiem jak pogodzić kontenery i hostowanie z jednego portu. To się wydaje sprzeczne.

0
adams0 napisał(a):

Dotychczas obsługiwałem to takim wpisem w expresie:

server.use((req, res, next) => {
    res.append('Access-Control-Allow-Origin', ['*']);
    res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.append('Access-Control-Allow-Headers', 'Content-Type');
    next();
});

Gdzieś przeczytałem że frontend i backend powinny być na osobnych kontenerach.
@Riddle: Prawie zgadłeś. Serwuję z 0.0.0.0:3000 a backend słucha na 0.0.0.0:3001
Są w ramach jednej sieci.
[...]
Dwa pierwsze wyszukiwania w googlu sugerują użycie innego portu dla backendu i frontendu:

Ale to że kontenery wystawiają inne porty, nie znaczy że Twój klient musi wiedzieć o tych dwóch portach.

Na pewno najlepszą praktyką byłoby to że Twoja ostateczna wersja aplikacji serwuje zarówno front jak i backend z jednego origina (np moja-strona.pl:80). Kolejną dobrą praktyką byłoby to, że środowisko developerskie i produkcyjne jest możliwie sobie jak najbliższe, więc to sugerowałoby że developerska wersja aplikacji również serwuje fronty na jednym porcie.

Produkcyjnie raczej nie wystawiałbym frontu na dockerze - raczej zbundlowałbym całą aplikację do statycznych plików .html, .js i .css, i po prostu serwował je. Backend możesz postawić albo na dockerze, albo "po prostu".

adams0 napisał(a):

Chciałbym to zrobić zgodnie z jakimiś dobrymi praktykami.
Czy to oznacza że muszę mieć jeden kontener z frontendem i backendem?

Sugeruję przeczytać mój post ponownie, tam znajdziesz odpowiedzi, a dokładniej:

  • Riddle napisał(a):

    To co powinieneś zrobić, moim zdaniem to:

    • Albo podczas developmentu serwować front z tego samego origina co backend (czyli coś w stylu front z localhost:3000 i backend localhost:3000), mógłbyś to ustawić w nginxie.
    • Albo podczas developmentu ustawić customowe-CORS, które potem musiałbyś wyłączyć na prodzie, i serwować aplikację i tak z jednego origina.
    • Albo wystawić w kontenerze z frontem server HTTP, niech front strzela do tego servera w kontenerze frontowym, a server w konenterze frontowym przekierowuje zapytania do servera backendowego; ale to też jest mega na około.

    Moim zdaniem lepsze będzie to pierwsze.

0

Musisz żądania wysyłać na localhost:3000 i na nginx przekierować na port 3001.

0

Okazało się że, tak jak myślałem, problem nie leżał w CORS'ie.
Zapomniałem że przecież fetch jest wysyłany od klienta którego DNS w przeciwieństwie tego wewnątrz sieci Docker nie wie co to backend:3001
Zmieniłem więc na 127.0.0.1:3001 i zaczęło śmigać.
Tak jak już wspominałem problem CORS'a rozwiązałem dopisaniem nagłówków w odpowiedzi.
Cieszę się że pokazaliście mi że tego się nie powinno robić na produkcji (plus dla tego który odpowie dla czego właściwie nie?)

Firefox z jakiegoś powodu wspomina o CORS w sytuacjach kiedy nie ma odpowiedzi od serwera.
Trochę zmyła.

1
adams0 napisał(a):

Tak jak już wspominałem problem CORS'a rozwiązałem dopisaniem nagłówków w odpowiedzi.

Czy tym nagłówkiem przypadkiem nie jest res.append('Access-Control-Allow-Origin', ['*']);? Bo jeśli tak to nic nie rozwiązałeś, tylko uciszyłeś problem.

0
res.append('Access-Control-Allow-Origin', ['*']);

Ten nagłówek w odpowiedzi jest po to, żeby powiedzieć z jakich domen możesz się dobić do serwera. Jeśli zrobisz * to z każdej domeny będziesz mógł puszczać requesty na Twoje endpointy. Zapewne widzisz, że na produkcji nie jest to pożądany efekt.

1
FrontendGuy napisał(a):

Ten nagłówek w odpowiedzi jest po to, żeby powiedzieć z jakich domen możesz się dobić do serwera. Jeśli zrobisz * to z każdej domeny będziesz mógł puszczać requesty na Twoje endpointy.

To zbyt duże uproszczenie To nawet nie uproszczenie. To błąd. Błędnie sugeruje, że ten nagłówek zabezpiecza serwer - bez nagłówka i tak będzie można z każdej domeny będziesz mógł puszczać requesty na Twoje endpointy, tylko nie z przeglądarek. SOP zabezpiecza nie serwer, tylko użytkownika przed zrobieniem sobie kuku.

W ogóle jak można, tak na logikę, wytłumaczyć w jaki sposób dodanie nagłówka przez serwer miałoby chronić serwer?

0

@Saalin:

  1. Jesteśmy w dziale Javascript. Myślałem, że to jasne, że chodzi o przeglądarkę.
  2. Nie napisałem nic o ochronie serwera

Dla sprostowania:

  1. Jeśli klient jest w tej samej domenie co serwer - wszystko jest OK (spełnione Same Origin Policy)
  2. Jeśli klient jest w innej domenie to żeby pobrać zasoby z serwera nasza domena musi znajdować sie w nagłowku 'Access-Control-Allow-Origin'
  3. Jeśli klient jest w innej domenie niż serwer i domena nie znajduje się w nagłówku 'Access-Control-Allow-Origin' to dostajemy CORS
0
FrontendGuy napisał(a):

@Saalin:

  1. Jesteśmy w dziale Javascript. Myślałem, że to jasne, że chodzi o przeglądarkę.

Od kiedy? A np. fetch wykonany z NodeJS?

Poza tym ok, widzę, że się rozumiemy, ale pytanie czy osoba postronna też zrozumie. Wg mnie powszechne jest niezrozumienie tego, bo skoro coś się konfiguruje po stronie serwera to wygląda jakby to serwer blokował requesty pochodzące z nieautoryzowanych źródeł. Temat przewijał się już parę razy na forum i właśnie tak niektórzy rozumieją CORS.

1

Odpowiedź od @Saalin jest najbliższa prawdy.

@FrontendGuy: W Twoim poście:

FrontendGuy napisał(a):

Jeśli zrobisz * to z każdej domeny będziesz mógł puszczać requesty na Twoje endpointy. Zapewne widzisz, że na produkcji nie jest to pożądany efekt.

napisałeś to z każdej domeny będziesz mógł puszczać requesty na Twoje endpointy, co nie jest prawdą - bo requesty możesz puszczać zawsze z wszędy. To jest popularny błąd w rozumieniu CORS, że ten header blokuje requesty. Ludzie dodają header - ich apka zaczyna działa - więc dochodzą do wnisoku że to header musiał zablokować request (co jest oczywiście bzdurą).

Jeśli o tym nie wiedziałeś, to @Saalin słusznie zwrócił Ci uwagę. Jeśli jednak o tym wiedziałeś, to powinieneś napisać to przeglądarka użytkownika przepuści request webowy na Twoje endpointy, bo Twoja wypowiedź wprowadza w błąd.

FrontendGuy napisał(a):

Dla sprostowania:

  1. Jeśli klient jest w tej samej domenie co serwer - wszystko jest OK (spełnione Same Origin Policy)
  2. Jeśli klient jest w innej domenie to żeby pobrać zasoby z serwera nasza domena musi znajdować sie w nagłowku 'Access-Control-Allow-Origin'
  3. Jeśli klient jest w innej domenie niż serwer i domena nie znajduje się w nagłówku 'Access-Control-Allow-Origin' to dostajemy CORS

Nic nie sprostowałeś, bo nadal mogę napisać klienta który będzie robił requesty i całkowicie olewał CORS, i żaden header nic tu nie pomoże.

Jeśli chcesz być pomocny, to musisz dodać w swoim sprostowaniu informację o tym że to PRZEGLĄDARKA blokuje te requesty w gestii usera, i to tylko przeglądarka która ma zaimplementowane CORS i ma je włączone.

Łatwo możesz wyłączyć CORS w np w Google Chrome uruchamiając go z flagą --disable-web-security i wtedy chrome nie sprawdzi żadnych headerów i przepuści każdy request.

Błąd który popełniasz @FrontendGuy to nie bierzesz pod uwagę, że istnieją inne klienty niż przeglądarki, i można wysłać request inaczej niż fetch()/AJAX. Jeśli zamkniesz swoje myślenie że przeglądarki to jedyne możliwe klienty (czyt. wszystkie możliwe klienty mają wbudowane i włączone CORS), to wychodzą właśnie takie kwiatki.

2

Nie czytałem wszystkiego co inni pisali,
Ale tak jak CORS wyłączysz to musisz się liczyć z tym, że trzymając dane autentykacyjne w http only to przeglądarka do każdego zapytania nawet nie z twojej strony pozwoli na to.

Ogólnie CORS powinno się naprawić w ten sposób, że masz jakieś reverse proxy np. nginx i ono przekierowuje gdy jest adres twojastrona.pl/ na jedne serwer i gdy jest twojastrona.pl/api/ na drugi i wtedy CORS nie jest złamany i masz zabezpieczenia.

Jak testujesz sobie na swoim kompie możesz mieć byle jak, ale potem musisz to jakoś elegenacko rozwiązać, ewentualnie możesz dodać nie '*' że wszystkie domeny tylko otwoją jedną, bo dodowanie wszystkcih co istnieją na świecie na pewnoe nie jest najlepsze, skoro masz tylko jeden frontend.

Ale i tak da się to rozwiązać pewnie lepiej, w ostateczności masz powiedzmy 3 rozwiązania i może więcej, ale zawsze jest jakieś wyjście z dziwnej sytuacji, dziwne i niezrozumiałe sytuacje powodują motywację, a ta działanie przez daną jednostkę.

1 użytkowników online, w tym zalogowanych: 0, gości: 1