CORS – co to jest, dlaczego przeglądarka blokuje żądania i jak naprawić błąd Cross-Origin

CORS — co to jest, dlaczego przeglądarka blokuje żądania i jak naprawić błąd Cross-Origin

Krótka odpowiedź

CORS (Cross-Origin Resource Sharing) to mechanizm bezpieczeństwa przeglądarki, który blokuje żądania JavaScript (fetch(), XMLHttpRequest) do innej domeny, portu lub protokołu, jeśli serwer nie odpowie nagłówkiem Access-Control-Allow-Origin dopasowanym do originu strony1. Dla żądań z niestandardowymi nagłówkami, metodami PUT/DELETE albo Content-Type: application/json przeglądarka najpierw wysyła żądanie OPTIONS, czyli preflight1. Błąd naprawiasz na serwerze, nie w przeglądarce – dodając prawidłowe nagłówki CORS do odpowiedzi API.

Budujesz frontend, wysyłasz żądanie fetch() do API i przeglądarka wyrzuca czerwony błąd: „Access to fetch at 'https://api.example.com’ from origin 'https://mysite.com’ has been blocked by CORS policy: No 'Access-Control-Allow-Origin’ header is present”. To samo zapytanie działa w Postmanie, w cURL i w terminalu – ale nie w przeglądarce, bo CORS to reguła egzekwowana wyłącznie po stronie klienta.

Co to jest „origin” i „cross-origin”

Origin (pochodzenie) to kombinacja: protokół + domena + port (protokół to np. różnica między HTTP a HTTPS). Dwa URL-e mają ten sam origin, jeśli wszystkie trzy elementy się zgadzają1:

URL A URL B Ten sam origin? Dlaczego
https://example.com/a https://example.com/b TAK Ten sam protokół, domena, port
https://example.com http://example.com NIE Inny protokół (https vs http)
https://example.com https://api.example.com NIE Inna subdomena
https://example.com https://example.com:8080 NIE Inny port
https://mysite.com https://api.example.com NIE Inna domena

Cross-origin = żądanie z jednego originu do innego. Przykład: Twój frontend na https://mysite.com wysyła fetch() do API na https://api.example.com – to jest cross-origin request i podlega CORS.

Dlaczego CORS istnieje – problem bezpieczeństwa

Bez CORS przeglądarka pozwalałaby dowolnej stronie wysyłać żądania do dowolnego serwera z cookies i tokenami użytkownika. Wyobraź sobie:

  1. Odwiedzasz złośliwą stronę evil.com
  2. JavaScript na evil.com wysyła fetch() do bank.com/api/transfer?amount=10000&to=hacker
  3. Przeglądarka automatycznie dołącza Twoje cookies (bo jesteś zalogowany na bank.com)
  4. Bank przetwarza przelew – bo widzi prawidłowe cookies

CORS temu zapobiega: przeglądarka blokuje cross-origin request, chyba że serwer (bank.com) wyraźnie powie „pozwalam na żądania z evil.com” – co oczywiście nie zrobi.

Ważne: CORS to mechanizm przeglądarki, nie serwera. Serwer odpowiada normalnie na każde żądanie (cURL, Postman, backend → backend). To przeglądarka sprawdza nagłówki CORS i blokuje odpowiedź, jeśli serwer nie pozwala1.

Jak CORS działa – mechanizm krok po kroku

Proste żądania (Simple Requests)

„Proste” żądanie nie wywołuje preflightu. Musi spełniać jednocześnie: metodę GET, HEAD lub POST, tylko bezpieczne nagłówki (Accept, Accept-Language, Content-Language, Content-Type) i – jeśli jest Content-Type – wartość ograniczoną do application/x-www-form-urlencoded, multipart/form-data lub text/plain1. Dla takiego żądania przeglądarka:

  1. Wysyła żądanie do serwera z nagłówkiem Origin: https://mysite.com
  2. Serwer odpowiada z nagłówkiem Access-Control-Allow-Origin: https://mysite.com (lub * = każdy origin)
  3. Przeglądarka sprawdza: czy Access-Control-Allow-Origin pasuje do mojego originu?
  4. Jeśli TAK → odpowiedź przekazana do JavaScript
  5. Jeśli NIE → odpowiedź zablokowana, JavaScript dostaje błąd CORS

Preflight request (OPTIONS)

Dla żądań spoza powyższych warunków (PUT, DELETE, custom nagłówki jak Authorization, Content-Type: application/json) przeglądarka najpierw wysyła żądanie OPTIONS (preflight)1:

  1. Przeglądarka: OPTIONS /api/data → „Hej serwer, czy mogę wysłać PUT z nagłówkiem Authorization z originu https://mysite.com?”
  2. Serwer: 204 No Content + nagłówki:
    • Access-Control-Allow-Origin: https://mysite.com
    • Access-Control-Allow-Methods: GET, POST, PUT, DELETE
    • Access-Control-Allow-Headers: Authorization, Content-Type
  3. Przeglądarka: „OK, serwer pozwala” → wysyła właściwe żądanie PUT

Preflight to „pytanie o pozwolenie” – jeśli serwer nie odpowie prawidłowo, właściwe żądanie nie jest wysyłane w ogóle.

Jak naprawić błąd CORS – konfiguracja serwera

Nagłówek Access-Control-Allow-Origin

Serwer musi zwracać nagłówek Access-Control-Allow-Origin z odpowiednią wartością:

# Pozwól na żądania z konkretnego originu:
Access-Control-Allow-Origin: https://mysite.com

# Pozwól na żądania z KAŻDEGO originu (mniej bezpieczne):
Access-Control-Allow-Origin: *

Uwaga: * (wildcard) nie działa z credentials: 'include' (cookies/auth). Jeśli Twój frontend wysyła cookies, musisz podać konkretny origin.

Ryzyko: Access-Control-Allow-Origin: * nigdy nie łączy się z Access-Control-Allow-Credentials: true – przeglądarka odrzuci taką kombinację, więc technicznie nie da się „przypadkiem” wysłać cookies przy wildcardzie2. Prawdziwe ryzyko jest odwrotne: część zespołów, próbując to obejść, dynamicznie odbija każdy nadchodzący Origin jako dozwolony (zamiast sprawdzać go z listą), co w praktyce daje efekt wildcardu z włączonymi credentials i otwiera API na dowolną domenę.

Apache (.htaccess)

# Pozwól na wszystkie originy:
Header set Access-Control-Allow-Origin "*"

# Pozwól na konkretny origin:
Header set Access-Control-Allow-Origin "https://mysite.com"

# Pełna konfiguracja (z preflight):
Header set Access-Control-Allow-Origin "https://mysite.com"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Authorization, Content-Type"

# Obsłuż preflight OPTIONS:
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]

Nginx

location /api/ {
    add_header Access-Control-Allow-Origin "https://mysite.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

    if ($request_method = OPTIONS) {
        return 204;
    }
}

Node.js (Express)

const cors = require('cors');

// Pozwól na wszystkie originy:
app.use(cors());

// Lub konkretny origin:
app.use(cors({
  origin: 'https://mysite.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Authorization', 'Content-Type'],
  credentials: true  // jeśli wysyłasz cookies
}));

WordPress (functions.php)

add_action('init', function() {
  header('Access-Control-Allow-Origin: https://mysite.com');
  header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
  header('Access-Control-Allow-Headers: Authorization, Content-Type');

  if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
  }
});

Lub użyj wtyczki WP CORS (wordpress.org) – konfiguracja z poziomu wp-admin.

Najczęstsze scenariusze i rozwiązania

Frontend React/Next.js → WordPress REST API

Frontend na https://app.example.com pobiera wpisy z REST API WordPress na https://blog.example.com/wp-json/wp/v2/posts. Rozwiązanie: dodaj nagłówek CORS w WordPress (functions.php lub .htaccess jak wyżej).

Frontend → zewnętrzne API (np. OpenAI, Stripe)

NIE wysyłaj żądań do zewnętrznych API bezpośrednio z frontendu – nawet jeśli naprawisz CORS, ujawnisz klucz API w kodzie JavaScript (widoczny dla każdego). Rozwiązanie: stwórz proxy na swoim serwerze (API Route w Next.js, endpoint w Express) – frontend wysyła do Twojego backendu, backend wysyła do zewnętrznego API z kluczem.

Localhost podczas developmentu

Frontend na http://localhost:3000 → API na http://localhost:8080. Różne porty = różne originy = CORS. Rozwiązania:

  • Proxy w devserver: Next.js: next.config.jsrewrites. Vite: vite.config.jsserver.proxy. Frontend wysyła do siebie, devserver proxy’uje na API.
  • CORS na backendzie (dev only): Access-Control-Allow-Origin: http://localhost:3000.
  • Rozszerzenie przeglądarki: „Allow CORS” (Chrome) – wyłącza CORS lokalnie. Tylko do developmentu!

Ważne nagłówki CORS – pełna lista

Nagłówek Co robi Przykład
Access-Control-Allow-Origin Dozwolony origin https://mysite.com lub *
Access-Control-Allow-Methods Dozwolone metody HTTP GET, POST, PUT, DELETE
Access-Control-Allow-Headers Dozwolone nagłówki żądania Authorization, Content-Type
Access-Control-Allow-Credentials Czy pozwolić na cookies true
Access-Control-Max-Age Jak długo cache preflight (sekundy) 86400 (24h)
Access-Control-Expose-Headers Które nagłówki odpowiedzi JS widzi X-Total-Count, Link

Nagłówki i ich działanie sprawdzone w dokumentacji MDN oraz w specyfikacji Fetch (WHATWG)13.

Najczęściej zadawane pytania

Dlaczego Postman/cURL nie mają problemu z CORS?

CORS to mechanizm przeglądarki, nie serwera ani protokołu HTTP jako takiego. Postman, cURL i połączenia backend-to-backend nie są przeglądarkami i nie implementują polityki CORS, więc to samo żądanie przechodzi bez przeszkód poza przeglądarką, a w JavaScript w przeglądarce zostaje zablokowane.

Czy Access-Control-Allow-Origin: * jest bezpieczne?

Dla publicznych API bez logowania (dane pogodowe, kursy walut, publiczne listy) – tak. Dla API z autentykacją (cookies, tokeny) nie da się tego bezpiecznie połączyć z credentials – przeglądarka odrzuca kombinację wildcardu z Access-Control-Allow-Credentials: true. Dla endpointów wymagających logowania serwer musi zwracać konkretny, dozwolony origin, nie gwiazdkę.

Jak pozwolić na wiele originów?

Nagłówek Access-Control-Allow-Origin akceptuje tylko jedną wartość, nie listę. Żeby obsłużyć wiele originów, serwer sprawdza nagłówek Origin przychodzącego żądania, porównuje go z listą dozwolonych domen i dynamicznie zwraca w odpowiedzi dokładnie ten origin, jeśli znajduje się na liście.

CORS a CDN?

CDN (Cloudflare, Bunny) cache’uje odpowiedzi razem z nagłówkami CORS. Jeśli CDN zapisze w cache’u odpowiedź z Access-Control-Allow-Origin ustawionym dla jednej domeny, inna domena może dostać tę samą, nieprawidłową dla niej odpowiedź z cache’u. Rozwiązaniem jest nagłówek Vary: Origin na serwerze – mówi CDN „trzymaj osobną wersję odpowiedzi w cache’u dla każdego originu”.

Źródła

Zweryfikowano: wrzesień 2026

Definicje, przykłady nagłówków i zasady preflightu sprawdziliśmy w dokumentacji MDN i specyfikacji Fetch.

  1. Cross-Origin Resource Sharing (CORS)DokumentacjaMDN Web Docsdeveloper.mozilla.org
  2. Reason: CORS header 'Access-Control-Allow-Origin’ does not match / credentialsDokumentacjaMDN Web Docsdeveloper.mozilla.org
  3. Fetch Standard – HTTP CORS protocol, CORS-safelisted methodsStandardWHATWGfetch.spec.whatwg.org
Picture of Tomasz Zieliński
Tomasz Zieliński

Tomasz zajmuje się tematyką SEO, sztucznej inteligencji i automatyzacji pracy w marketingu internetowym. W swoich artykułach analizuje zmiany w algorytmach wyszukiwarek, rozwój narzędzi AI oraz nowe sposoby tworzenia i optymalizacji treści. Interesuje go przede wszystkim to, jak technologia wpływa na codzienną pracę specjalistów SEO, marketerów i twórców internetowych.

Facebook
Twitter
LinkedIn
Pinterest

Najnowsze Wpisy

Śledź nas