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
Błąd "Access to fetch has been blocked by CORS policy" to jeden z najczęstszych problemów frontend developerów. Wyjaśniam co to jest CORS, dlaczego przeglądarka blokuje żądania i jak skonfigurować serwer, żeby wszystko działało.

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”. Twoje API działa w Postmanie, w cURL, w terminalu — ale nie w przeglądarce. Dlaczego?

Ponieważ CORS (Cross-Origin Resource Sharing) to mechanizm bezpieczeństwa przeglądarek, który blokuje żądania HTTP między różnymi domenami — chyba że serwer docelowy wyraźnie na to pozwoli. W tym poradniku wyjaśniam jak CORS działa, dlaczego istnieje i jak go skonfigurować na serwerze.

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

Origin (pochodzenie) to kombinacja: protokół + domena + port. Dwa URL-e mają ten sam origin, jeśli wszystkie trzy elementy się zgadzają:

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

Jak CORS działa — mechanizm krok po kroku

Proste żądania (Simple Requests)

Dla prostych żądań (GET/POST z basic nagłówkami) 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 „złożonych” żądań (PUT, DELETE, custom nagłówki jak Authorization, Content-Type: application/json) przeglądarka najpierw wysyła żądanie OPTIONS (preflight):

  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.

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

Najczęściej zadawane pytania

Dlaczego Postman/cURL nie mają problemu z CORS?

CORS to mechanizm przeglądarki. Postman, cURL, backend-to-backend — nie są przeglądarkami i nie implementują CORS. Dlatego żądanie działa w terminalu, ale nie w JavaScript w przeglądarce.

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

Dla publicznych API (dane pogodowe, kursy walut, publiczne listy) — tak. Dla API z autentykacją (cookies, tokeny) — nie. Wildcard * nie pozwala na credentials: true, więc cookies nie zostaną wysłane — ale mimo to lepiej podać konkretny origin dla API wymagających auth.

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: na serwerze sprawdź nagłówek Origin żądania, porównaj z listą dozwolonych i dynamicznie ustaw Access-Control-Allow-Origin na pasujący origin.

CORS a CDN?

CDN (Cloudflare, Bunny) cache’uje odpowiedzi z nagłówkami CORS. Problem: jeśli CDN cache’uje odpowiedź z Access-Control-Allow-Origin: https://siteA.com, to siteB.com dostanie tę samą cached odpowiedź z niewłaściwym originem. Rozwiązanie: nagłówek Vary: Origin na serwerze — mówi CDN „cache osobną wersję per origin”.

Podsumowanie

CORS to mechanizm bezpieczeństwa przeglądarki, który blokuje cross-origin requests — żądania między różnymi domenami/portami/protokołami. Naprawa: dodaj nagłówek Access-Control-Allow-Origin na serwerze (Apache: .htaccess, Nginx: config, Node.js: cors middleware, WordPress: functions.php). Dla prostych żądań: wystarczy sam nagłówek. Dla złożonych (PUT, DELETE, custom headers): serwer musi obsłużyć też preflight OPTIONS. Podczas developmentu: proxy w devserver lub Access-Control-Allow-Origin: http://localhost:3000. I pamiętaj: CORS chroni użytkowników — nie wyłączaj go na produkcji „bo przeszkadza”.

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