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.
Spis treści
ToggleCo 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:
- Odwiedzasz złośliwą stronę
evil.com - JavaScript na
evil.comwysyła fetch() dobank.com/api/transfer?amount=10000&to=hacker - Przeglądarka automatycznie dołącza Twoje cookies (bo jesteś zalogowany na bank.com)
- 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:
- Wysyła żądanie do serwera z nagłówkiem
Origin: https://mysite.com - Serwer odpowiada z nagłówkiem
Access-Control-Allow-Origin: https://mysite.com(lub*= każdy origin) - Przeglądarka sprawdza: czy
Access-Control-Allow-Originpasuje do mojego originu? - Jeśli TAK → odpowiedź przekazana do JavaScript
- 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):
- Przeglądarka: OPTIONS /api/data → „Hej serwer, czy mogę wysłać PUT z nagłówkiem Authorization z originu https://mysite.com?”
- Serwer: 204 No Content + nagłówki:
Access-Control-Allow-Origin: https://mysite.comAccess-Control-Allow-Methods: GET, POST, PUT, DELETEAccess-Control-Allow-Headers: Authorization, Content-Type
- 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.js→rewrites. Vite:vite.config.js→server.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”.






