CORS HTTP hlavičky
Cross-Origin Resource Sharing (CORS) je mechanismus, který umožňuje webovým stránkám bezpečně přistupovat ke zdrojům z jiné domény, než ze které byly načteny. Bez CORS by prohlížeč takové požadavky blokoval kvůli politice Same-Origin Policy (SOP), která je jedním ze základních pilířů bezpečnosti webu.
V tomto článku si vysvětlíme, jak CORS funguje na úrovni HTTP, projdeme si všechny důležité hlavičky, ukážeme si rozdíl mezi jednoduchými a tzv. preflight požadavky a nakonec implementujeme robustní řešení v PHP.
Co je Same-Origin Policy a proč CORS existuje
Než se ponoříme do CORS, je potřeba pochopit, proti čemu vlastně chrání. Same-Origin Policy říká, že JavaScript běžící na doméně example.com nemůže standardně číst odpovědi z požadavků směřujících na api.jine-domene.com. Origin je definován trojicí:
- schéma (
httpvshttps) - hostname (doména)
- port
Pokud se jakákoli z těchto tří částí liší, jedná se o cross-origin požadavek.
Kdyby SOP neexistovala, mohla by škodlivá stránka, kterou si otevřete, na pozadí poslat požadavek například na vaši internetovou banku — s vašimi cookies — a přečíst si odpověď. CORS je řízenou výjimkou z této politiky: server může explicitně povolit konkrétním cizím doménám přístup ke svým zdrojům.
Důležité: CORS je vynucován prohlížečem, nikoli serverem. Server odpověď stejně pošle, ale prohlížeč ji JavaScriptu nezpřístupní, pokud chybí správné hlavičky. Nástroje jako
curlnebo serverové HTTP klienty CORS ignorují.
Jednoduché vs. preflight požadavky
CORS rozlišuje dva typy požadavků a chování prohlížeče se mezi nimi výrazně liší.
Jednoduché požadavky (simple requests)
Požadavek je považován za "jednoduchý", pokud splňuje všechny následující podmínky:
- Metoda je
GET,HEADneboPOST - Hlavičky obsahují pouze tzv. CORS-safelisted hodnoty (
Accept,Accept-Language,Content-Language,Content-Type) Content-Typemá hodnotu pouzeapplication/x-www-form-urlencoded,multipart/form-datanebotext/plain
V tomto případě prohlížeč pošle požadavek rovnou a teprve podle odpovědi rozhodne, zda ji JavaScriptu zpřístupní.
Preflight požadavky
Pokud požadavek nesplňuje podmínky pro jednoduchý — typicky používá PUT, DELETE, PATCH nebo posílá JSON s Content-Type: application/json — prohlížeč nejprve pošle preflight požadavek metodou OPTIONS. Tím se serveru zeptá, zda je vůbec možné požadovanou operaci provést.
Schéma preflight komunikace:
Prohlížeč → Server: OPTIONS /api/users
Origin: https://moje-aplikace.cz
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Content-Type, Authorization
Server → Prohlížeč: HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://moje-aplikace.cz
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Prohlížeč → Server: DELETE /api/users/42
(skutečný požadavek)
Až po úspěšném preflightu prohlížeč pošle skutečný požadavek.
Hlavní CORS hlavičky
| Hlavička | Účel |
|---|---|
Access-Control-Allow-Origin |
Určuje, které originy mají přístup. Hodnota * povoluje všem, jinak konkrétní URL. |
Access-Control-Allow-Credentials |
Pokud je true, prohlížeč povolí poslat cookies a HTTP autentizaci. |
Access-Control-Allow-Methods |
Seznam povolených HTTP metod (např. GET, POST, PUT, DELETE). Používá se v odpovědi na preflight. |
Access-Control-Allow-Headers |
Hlavičky, které smí klient v požadavku použít. |
Access-Control-Expose-Headers |
Hlavičky, které smí JavaScript číst z odpovědi (jinak vidí jen základní set). |
Access-Control-Max-Age |
Jak dlouho (v sekundách) si prohlížeč může cachovat výsledek preflightu. |
Pozor na kombinaci wildcard a credentials
Toto je nejčastější chyba, na kterou vývojáři narazí:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Tato kombinace nefunguje. Pokud chcete posílat cookies (credentials: 'include' ve fetch API), musíte vrátit konkrétní origin, ne hvězdičku. Prohlížeč v opačném případě požadavek odmítne.
Implementace CORS v PHP
Následující implementace pokrývá nejčastější scénáře a ošetřuje i bezpečnostní úskalí. Místo slepého echování HTTP_ORIGIN zpět používá whitelist povolených domén.
final class CorsHandler{/** @var string[] Povolené originy — nikdy nedůvěřujte slepě tomu, co přijde od klienta */private const ALLOWED_ORIGINS = ['https://moje-aplikace.cz','https://www.moje-aplikace.cz','http://localhost:3000', // pro lokální vývoj];private const ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];private const ALLOWED_HEADERS = ['Content-Type', 'Authorization', 'X-Requested-With'];private const MAX_AGE = 86400; // 24 hodinpublic static function handle(): void{$origin = $_SERVER['HTTP_ORIGIN'] ?? null;// Origin neznáme nebo není v whitelistu — neposíláme žádné CORS hlavičkyif ($origin === null || !in_array($origin, self::ALLOWED_ORIGINS, true)) {return;}header('Access-Control-Allow-Origin: ' . $origin);header('Access-Control-Allow-Credentials: true');header('Vary: Origin'); // důležité pro caching proxy$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';if ($method === 'OPTIONS') {header('Access-Control-Allow-Methods: ' . implode(', ', self::ALLOWED_METHODS));header('Access-Control-Allow-Headers: ' . implode(', ', self::ALLOWED_HEADERS));header('Access-Control-Max-Age: ' . self::MAX_AGE);http_response_code(204);exit;}}}// Použití na začátku entry pointu (např. index.php)CorsHandler::handle();
Co tato implementace dělá lépe oproti naivnímu řešení
- Whitelist místo echa. Slepé vracení
Originhlavičky útočníkovi efektivně vypíná CORS ochranu — útočníkův prohlížeč prostě dostane tu hlavičku, kterou si přeje. - Hlavička
Vary: Originzajistí, že caching proxy (CDN, reverse proxy) neuloží odpověď s CORS hlavičkami pro jednu doménu a pak ji nepošle požadavku z jiné. - HTTP status
204 No Contentje sémanticky správnější odpověď na preflight než výchozí200. Access-Control-Max-Agesnižuje počet preflight požadavků a zlepšuje výkon API.
Když používáte framework
Většina moderních PHP frameworků má CORS middleware už hotový:
- Symfony — balíček
nelmio/cors-bundle - Laravel — vestavěný
HandleCorsmiddleware - Slim / Mezzio — middleware
tuupola/cors-middleware
Vlastní implementaci pište jen tehdy, kdy nepoužíváte framework nebo máte specifické požadavky.
Časté chyby a jejich diagnostika
"CORS error" v konzoli prohlížeče
Pokud v DevTools vidíte chybu typu "has been blocked by CORS policy", projděte si tento checklist:
- Vrací server hlavičku
Access-Control-Allow-Origin? Zkontrolujte odpověď v záložce Network. - Sedí origin přesně?
https://example.comahttps://www.example.comjsou různé originy. - Posíláte cookies a máte konkrétní origin? Wildcard
*scredentials: truenefunguje. - Selhal preflight? Pokud
OPTIONSpožadavek vrátí 4xx/5xx, skutečný požadavek se vůbec nepošle. - Není problém na proxy/CDN? Některé reverse proxy strippují CORS hlavičky, jiné je naopak duplikují.
CORS není autorizace
Nejdůležitější věc na závěr: CORS není bezpečnostní mechanismus pro server. Chrání pouze uživatele prohlížeče před nežádoucím čtením dat skripty z jiných domén. Server musí mít vlastní autentizaci a autorizaci — typicky tokeny, session, OAuth — protože nic nebrání útočníkovi v poslání požadavku přímo z curl nebo z vlastního backendu.
Závěr
CORS je elegantní řešení problému, který vznikl tím, jak otevřeným ekosystémem se web stal. Pochopení rozdílu mezi jednoduchými a preflight požadavky, znalost klíčových hlaviček a vědomí, že CORS chrání prohlížeč a ne server, vám ušetří hodiny ladění "záhadných" chyb.
Při návrhu API si dopředu rozmyslete, kdo k němu má mít přístup, používejte whitelist místo wildcardu, nezapomeňte na Vary: Origin a tam, kde můžete, sáhněte po vyzkoušeném middleware z frameworku.