Przeglądarka blokuje odczyt danych z obcego serwera, choć samo API działa poprawnie? Najczęściej odpowiada za to CORS, czyli mechanizm, który pozwala serwerowi określić, z jakich źródeł aplikacja może bezpiecznie odczytywać odpowiedzi. Wyjaśniam, jak działa, czym różni się zwykłe żądanie od preflightu, jak skonfigurować CORS w Pythonie i dlaczego zbyt szerokie reguły mogą osłabić bezpieczeństwo.
CORS określa, które aplikacje mogą czytać odpowiedzi API
- Mechanizm przeglądarki kontroluje dostęp między różnymi źródłami.
- Na źródło składają się schemat, host i port.
- Przy bardziej złożonych żądaniach przeglądarka wysyła wcześniej żądanie OPTIONS preflight.
- Najważniejszy nagłówek to Access-Control-Allow-Origin.
- Gwiazdka nie pasuje do żądań z ciasteczkami lub innymi poświadczeniami.
- CORS nie zastępuje autoryzacji ani ochrony przed CSRF.
Czym jest CORS i skąd bierze się problem
CORS oznacza Cross-Origin Resource Sharing, czyli współdzielenie zasobów między różnymi źródłami. To zestaw reguł opartych na nagłówkach HTTP, dzięki którym serwer informuje przeglądarkę, czy kod JavaScript może odczytać zwrócone dane.
Domyślnie działa tutaj zasada same-origin policy. Ogranicza ona skrypty uruchomione na jednej stronie, aby nie mogły swobodnie czytać danych z innych aplikacji. Trudno uznać to za zbędną przeszkodę. Bez takiej ochrony przypadkowa strona mogłaby próbować odczytywać informacje z panelu bankowego lub firmowego systemu otwartego w tej samej przeglądarce.
Origin to nie tylko domena
Źródło, nazywane też originem, tworzą trzy elementy: schemat, host i port. Z tego powodu aplikacje działające na `http://localhost:3000` i `http://localhost:8000` mają różne źródła, mimo że korzystają z tego samego komputera.
| Przykład | Czy to to samo źródło | Powód |
|---|---|---|
| http://localhost:3000 i http://localhost:8000 | Nie | Inny port |
| http://app.example i https://app.example | Nie | Inny schemat |
| https://app.example i https://api.example | Nie | Inny host |
| https://app.example i https://app.example | Tak | Identyczny origin |
CORS nie jest zaporą dla całego internetu
Reguły CORS egzekwuje przede wszystkim przeglądarka. Narzędzia takie jak klient HTTP, skrypt backendowy czy polecenie wysyłające żądanie bezpośrednio do API zwykle nie stosują tych ograniczeń. Dlatego fakt, że żądanie działa w Postmanie, nie oznacza jeszcze, że zadziała w kodzie JavaScript.
CORS kontroluje możliwość odczytania odpowiedzi przez skrypt. Nie jest systemem logowania i nie decyduje samodzielnie, czy użytkownik ma prawo wykonać operację. To rozróżnienie ma ogromne znaczenie przy projektowaniu bezpiecznego API.

Jak przeglądarka sprawdza zgodę serwera
Przeglądarka dołącza do żądania nagłówek Origin. Serwer powinien odpowiedzieć nagłówkiem określającym, czy dane źródło jest dozwolone. Jeśli odpowiedź nie zawiera właściwej zgody, kod JavaScript zobaczy błąd CORS zamiast danych.
Żądanie proste
Niektóre żądania mogą zostać wysłane od razu. Dotyczy to między innymi wybranych żądań GET, HEAD i POST, które używają dozwolonych nagłówków oraz typów danych. Przeglądarka nadal sprawdza odpowiedź, ale nie musi wcześniej pytać serwera o pozwolenie.
Preflight, czyli kontrola przed właściwym żądaniem
Jeśli żądanie używa na przykład metody PUT, PATCH lub DELETE, nagłówka Authorization albo typu application/json, przeglądarka może najpierw wysłać żądanie OPTIONS. Ten etap nazywa się preflightem.
- Przeglądarka wysyła OPTIONS z informacją o źródle, metodzie i planowanych nagłówkach.
- Serwer odpowiada, jakie źródła, metody i nagłówki akceptuje.
- Dopiero po pozytywnej odpowiedzi przeglądarka wysyła właściwe żądanie.
- Jeśli preflight się nie powiedzie, kod aplikacji nie otrzyma normalnej odpowiedzi.
Przykładowe żądanie kontrolne może zawierać Access-Control-Request-Method: POST oraz Access-Control-Request-Headers: Content-Type, Authorization. Serwer musi wtedy wyraźnie dopuścić te elementy, inaczej przeglądarka zatrzyma dalszy etap.
W praktyce wiele problemów wynika z pominięcia obsługi metody OPTIONS. Backend zwraca wtedy kod 404 lub 405, chociaż właściwy endpoint POST działa bez zarzutu. To jeden z pierwszych elementów, które sprawdzam podczas diagnozowania błędu.
Najważniejsze nagłówki CORS bez zbędnej magii
Konfiguracja opiera się na kilku nagłówkach odpowiedzi. Nie trzeba stosować wszystkich w każdym projekcie. Najbezpieczniej ustawić tylko te, które są potrzebne do konkretnego scenariusza.
| Nagłówek | Do czego służy | Praktyczna wskazówka |
|---|---|---|
Access-Control-Allow-Origin |
Wskazuje dozwolone źródło | Użyj konkretnego originu, gdy API nie jest publiczne |
Access-Control-Allow-Methods |
Określa akceptowane metody | Dodaj tylko metody faktycznie używane |
Access-Control-Allow-Headers |
Dopuszcza niestandardowe nagłówki żądania | Często potrzebny dla Content-Type i Authorization |
Access-Control-Allow-Credentials |
Pozwala udostępnić odpowiedź przy poświadczeniach | Wartość musi wynosić true
|
Access-Control-Expose-Headers |
Udostępnia JavaScriptowi wybrane nagłówki odpowiedzi | Przydatny na przykład dla własnego nagłówka z identyfikatorem |
Access-Control-Max-Age |
Określa czas zapamiętania wyniku preflightu | Zmniejsza liczbę dodatkowych żądań OPTIONS |
Najprostsza odpowiedź dla publicznego API bez ciasteczek może wyglądać tak:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: Content-Type
Gwiazdka oznacza dowolne źródło, ale ma sens głównie przy publicznych danych bez poświadczeń. Gdy aplikacja wysyła ciasteczka lub korzysta z uwierzytelniania sesyjnego, trzeba podać konkretny origin. Połączenie Access-Control-Allow-Origin: * z Access-Control-Allow-Credentials: true zostanie odrzucone przez przeglądarkę.
Jeśli serwer dynamicznie wybiera dozwolone źródło z listy, powinien zwykle dodać także Vary: Origin. Informuje to pamięć podręczną, że odpowiedź może być różna zależnie od nagłówka Origin. Bez tego można otrzymać nieprawidłową wersję odpowiedzi z cache.
Konfiguracja CORS w aplikacji Python
W Pythonie najwygodniej korzystać z middleware dostarczanego przez framework. Kluczowa zasada pozostaje jednak taka sama: lista zaufanych źródeł powinna być jawna, a nie tworzona przez bezrefleksyjne odbijanie wartości z nagłówka Origin.
FastAPI
W FastAPI konfigurację dodaje się zwykle na poziomie aplikacji. Przykład dopuszcza panel działający pod jednym, konkretnym adresem:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
trusted_origins = [
"https://panel.example"
]
app.add_middleware(
CORSMiddleware,
allow_origins=trusted_origins,
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Content-Type", "Authorization"],
)
Jeśli logowanie odbywa się tokenem przesyłanym w nagłówku, Authorization musi znaleźć się na liście dozwolonych nagłówków. Przy sesji opartej na ciasteczkach potrzebne jest również allow_credentials=True oraz poprawne ustawienia ciasteczka, między innymi SameSite i .
Flask
We Flasku popularnym rozwiązaniem jest rozszerzenie konfigurujące nagłówki automatycznie:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(
app,
resources={
r"/api/*": {
"origins": ["https://panel.example"]
}
},
supports_credentials=True
)
Ograniczenie reguły do ścieżki /api/* jest rozsądniejsze niż otwarcie CORS dla całej aplikacji. Endpoint zwracający dane publiczne może mieć inną politykę niż panel administracyjny, dlatego konfigurację dobrze dopasować do konkretnego zasobu.
Najczęstsze błędy i szybka diagnostyka
Brak nagłówka po stronie serwera
Komunikat o braku Access-Control-Allow-Origin oznacza najczęściej, że serwer nie dodał zgody do odpowiedzi albo dodał ją tylko dla właściwego żądania, pomijając OPTIONS. Sprawdź odpowiedź w zakładce Network i zobacz, czy nagłówek występuje zarówno przy preflighcie, jak i przy właściwym żądaniu.
Źródło różni się drobnym szczegółem
Origin musi pasować dokładnie. Różnicę robi port, schemat, końcowy zapis hosta lub środowisko. Adres deweloperski z portem 3000 nie jest tym samym co wersja produkcyjna działająca bez tego portu. Warto też pamiętać, że `http` i `https` nie są wymienne.
Gwiazdka przy ciasteczkach
To bardzo częsta pomyłka. Jeśli frontend wysyła żądanie z credentials: "include", serwer musi zwrócić konkretny origin oraz Access-Control-Allow-Credentials: true. Samo ustawienie opcji po stronie frontendu nie wystarczy.
Próba naprawy przez no-cors
Ustawienie mode: "no-cors" zwykle nie rozwiązuje problemu. Odpowiedź staje się wtedy nieprzejrzysta, czyli kod JavaScript nie może odczytać jej treści ani statusu w normalny sposób. Ta opcja nadaje się tylko do wąskich scenariuszy, w których nie potrzebujesz analizować odpowiedzi.
Przeczytaj również: React Virtual DOM - Jak działa i kiedy naprawdę pomaga UX?
Prosta kolejność sprawdzania
- Porównaj origin frontendu z listą dozwolonych źródeł na backendzie.
- Sprawdź, czy preflight OPTIONS otrzymuje kod sukcesu, zwykle 200 lub 204.
- Porównaj metodę i nagłówki żądania z wartościami w odpowiedzi serwera.
- Zweryfikuj ustawienia credentials po obu stronach.
- Sprawdź, czy proxy, load balancer albo serwer produkcyjny nie usuwa nagłówków CORS.
Nie próbuję zgadywać na podstawie samego komunikatu w konsoli. Najwięcej informacji daje porównanie dwóch konkretnych żądań w narzędziach deweloperskich, zwłaszcza nagłówków Origin, Access-Control-Request-Method i Access-Control-Allow-Origin.
Dlaczego CORS nie zastępuje autoryzacji
CORS ogranicza to, co przeglądarka udostępni skryptowi, ale nie zastępuje kontroli dostępu na serwerze. Złośliwa strona może w niektórych scenariuszach doprowadzić do wysłania żądania zmieniającego dane, nawet jeśli później nie otrzyma możliwości odczytania odpowiedzi.
Dlatego przy operacjach takich jak zmiana hasła, przelew czy usunięcie konta potrzebna jest osobna ochrona CSRF, poprawna autoryzacja i walidacja danych po stronie backendu. CORS nie powinien być traktowany jako jedyna bariera bezpieczeństwa.
Najmniej ryzykowna konfiguracja wygląda zwykle tak:
- dla publicznych danych bez logowania można użyć ściśle kontrolowanego dostępu publicznego,
- dla panelu użytkownika należy wskazać konkretne originy,
- dla żądań z ciasteczkami trzeba skonfigurować credentials i ochronę CSRF,
- metody oraz nagłówki powinny być ograniczone do rzeczywiście potrzebnych,
- nie należy automatycznie akceptować każdego originu przesłanego przez klienta.
Odbijanie nagłówka Origin bez sprawdzenia go na allowliście wygląda wygodnie, ale może otworzyć dostęp dowolnej stronie. W systemach przetwarzających dane użytkowników taki skrót jest zwykle złym kompromisem.
Najpierw zaufane źródła, potem otwieranie API
CORS można sprowadzić do jednej praktycznej decyzji: które aplikacje naprawdę muszą czytać odpowiedzi tego API? Gdy odpowiesz na to pytanie, ustaw konkretną listę originów, obsłuż OPTIONS, dopasuj metody i nagłówki oraz osobno zadbaj o uwierzytelnianie i CSRF.
Sam błąd CORS nie świadczy jeszcze o awarii API. Często oznacza, że przeglądarka prawidłowo zatrzymała odczyt odpowiedzi, bo serwer nie wyraził wystarczająco precyzyjnej zgody. Dobra konfiguracja nie polega na wyłączeniu zabezpieczenia, tylko na udostępnieniu dokładnie tego, co jest potrzebne.
