CORS začíná u same-origin policy
Same-origin policy je základní bezpečnostní mechanismus prohlížeče. Omezuje možnost dokumentu nebo skriptu číst data z jiného originu a brání například škodlivému webu číst data ze služby, kde je uživatel přihlášen. [3]
CORS, Cross-Origin Resource Sharing, je HTTP mechanismus, kterým server tuto zásadu kontrolovaně uvolňuje a určuje, které originy mohou odpověď číst. Fetch Standard ho popisuje jako opt-in protokol. [1][2]
Co přesně znamená origin
| Stránka | Cílová URL | Vztah | Proč |
|---|---|---|---|
| https://app.example.com | https://app.example.com/api | Stejný origin | Stejné schéma, host a port |
| https://app.example.com | https://api.example.com | Jiný origin | Jiný host |
| https://app.example.com | http://app.example.com | Jiný origin | Jiné schéma |
| https://app.example.com | https://app.example.com:8443 | Jiný origin | Jiný port |
| https://app.example.com | https://app.example.com/v2 | Stejný origin | Mění se pouze cesta |
Origin je dán schématem, hostem a portem. Dvě URL jsou same-origin pouze tehdy, když se shodují všechny tři hodnoty. Cesta nehraje roli. [3][4]
`https://app.example.com` a `https://api.example.com` jsou tedy různé originy. Stejně tak HTTP proti HTTPS nebo různé porty. [4]
Same-origin a same-site nejsou totožné pojmy. To je důležité u cookies, protože `SameSite` používá pojem site, zatímco CORS používá origin. [13]
Co opravdu znamená, že prohlížeč blokuje API
Tvrzení, že prohlížeč blokuje request, je často příliš zjednodušené. U jednoduchého cross-origin requestu může request odeslat, přijmout platnou HTTP odpověď a teprve poté ji nezpřístupnit JavaScriptu, pokud CORS kontrola selže. [1][6]
U requestů s preflightem je průběh jiný. Nejprve se posílá `OPTIONS`. Pokud preflight selže, skutečný request se neodešle. [2][5][6]
Tento rozdíl je důležitý u operací měnících data. CORS chyba automaticky neznamená, že backend žádný request nedostal.
Jednoduché requesty: kdy se preflight nepoužije
| Metoda | Příklad headers | Jednoduchý request | Důsledek |
|---|---|---|---|
| GET | Accept | Ano | Bez preflightu |
| POST | Content-Type: text/plain | Ano | Bez preflightu |
| POST | Content-Type: application/json | Ne | JSON vyvolá preflight |
| PUT | Content-Type: application/json | Ne | Metoda vyvolá preflight |
| GET | Authorization: Bearer ... | Ne | Header vyvolá preflight |
Request je jednoduchý jen při splnění CORS safelist. Povolené metody jsou `GET`, `HEAD` a `POST`. Ručně nastavené headers musí být safelisted a `Content-Type` smí být jen `application/x-www-form-urlencoded`, `multipart/form-data` nebo `text/plain`. [1]
Běžný `POST` s `Content-Type: application/json` není jednoduchý a obvykle vyvolá preflight. Stejně tak `PUT`, `DELETE` nebo vlastní header typu `X-Request-ID`. [1][11]
Preflight OPTIONS: co prohlížeč ověřuje
Preflight je automatický `OPTIONS` request. Prohlížeč posílá `Origin`, `Access-Control-Request-Method` a podle potřeby `Access-Control-Request-Headers`. Server odpoví povolenými originy, metodami a headers. [2][5]
Pokud chce aplikace poslat `DELETE` s `Authorization`, může prohlížeč nejprve ověřit, zda daný origin smí použít tuto metodu a header. Teprve kladná odpověď dovolí skutečný request. [5]
Preflight přidává další síťový round trip, ale výsledek lze uložit do samostatné CORS preflight cache mimo běžnou HTTP cache. [5][10]
Nejdůležitější CORS headers
| Header | Směr | Účel |
|---|---|---|
| Origin | Request | Origin, který request iniciuje |
| Access-Control-Allow-Origin | Response | Origin, jehož kód může odpověď obdržet |
| Access-Control-Allow-Methods | Preflight response | Metody povolené preflightem |
| Access-Control-Allow-Headers | Preflight response | Request headers povolené preflightem |
| Access-Control-Allow-Credentials | Response | Povolení zpřístupnit odpověď s credentials |
| Access-Control-Expose-Headers | Response | Další response headers dostupné JavaScriptu |
| Access-Control-Max-Age | Preflight response | Doba cache výsledku preflightu |
| Vary: Origin | Response | Informace pro cache, že odpověď závisí na Origin |
`Origin` je request header označující iniciující origin. `Access-Control-Allow-Origin` je serverový response header, který říká prohlížeči, zda může odpověď sdílet s kódem z daného originu. [1][2][8]
`Access-Control-Allow-Methods` a `Access-Control-Allow-Headers` jsou důležité hlavně při preflightu. `Access-Control-Allow-Credentials` povoluje zpřístupnění odpovědi s credentials a `Access-Control-Expose-Headers` zpřístupní JavaScriptu další response headers. [1][2]
Pokud server nastavuje `Access-Control-Allow-Origin` dynamicky, MDN doporučuje také `Vary: Origin`. [1]
`Access-Control-Allow-Origin`: wildcard nebo konkrétní origin
`Access-Control-Allow-Origin: *` dovoluje sdílení odpovědi s libovolným originem u requestů bez credentials. Pro skutečně veřejné API to může být správně. [2][8]
U privátních nebo citlivých endpointů by povolená množina měla být co nejmenší. MDN a OWASP doporučují konkrétní originy, pokud není globální přístup nutný. [14][17]
Při více povolených originech má server porovnat příchozí `Origin` s allowlistem a vrátit jen schválenou hodnotu. Slepé zrcadlení libovolného originu je nebezpečné. [8][16]
Cookies a credentials: častý zdroj problémů
Fetch může použít `credentials: "include"` pro credentials v cross-origin requests. To samo nestačí. Server musí vrátit `Access-Control-Allow-Credentials: true` a konkrétní `Access-Control-Allow-Origin`. `*` zde není povoleno. [2][6][9]
Cookies podléhají také vlastním pravidlům. `SameSite` může zabránit jejich odeslání při cross-site requestu a `SameSite=None` vyžaduje `Secure`. [13]
CORS a `SameSite` řeší různé problémy. CORS může být správně, ale cookie chybí, nebo cookie může odejít a CORS stále zablokuje JavaScriptu odpověď.
Proč `mode: "no-cors"` obvykle nic neopraví
`fetch(..., { mode: "no-cors" })` neobchází CORS, pokud aplikace potřebuje odpověď číst. Metody a headers jsou omezené a odpověď je opaque. JavaScript nemůže číst body ani headers a viditelný status je `0`. [6][7][11]
`no-cors` má specializované použití, například některé scénáře Service Worker, ale pro běžné JSON API je obvykle špatnou volbou. [6]
Pokud externí API CORS nenabízí, vlastní backend nebo proxy může provést request server-to-server. [11][12]
Proč curl, Postman nebo backend mohou fungovat
Same-origin policy a CORS vynucuje prohlížeč. HTTP klient mimo tento model stejnou CORS kontrolou neprochází. Proto může endpoint fungovat v curl nebo API nástroji a z JavaScriptu stránky být blokovaný. [2][3][16]
To také ukazuje, proč CORS není autentizace API. Klient mimo prohlížeč může nastavit vlastní `Origin`, takže OWASP varuje před použitím tohoto headeru jako důkazu identity. [16]
API stále potřebuje autorizaci, tokeny, sessions, kontroly oprávnění a serverovou validaci.
CORS nenahrazuje CSRF ochranu ani autorizaci
Same-origin policy omezuje hlavně cross-origin čtení. Formuláře a některé jednoduché requesty lze přesto posílat cross-origin, takže cookie-based aplikace stále potřebují CSRF ochranu. [3][15]
OWASP výslovně doporučuje nespoléhat pouze na CORS nebo `Origin` při ochraně citlivých zdrojů. Autentizace a autorizace jsou nutné nezávisle. [16]
CORS odpovídá na otázku, zda prohlížeč může zpřístupnit odpověď kódu z originu. Neřeší, zda uživatel nebo klient smí operaci provést.
Časté chyby a diagnostika
Chybějící `Access-Control-Allow-Origin` je běžná chyba. Další příčiny jsou nepovolená metoda, chybějící položka v `Access-Control-Allow-Headers`, `*` s credentials, chybná OPTIONS odpověď nebo redirecty v CORS flow. [11][12]
JavaScript dostává záměrně jen omezené detaily. Pro přesnou diagnózu je důležitější konzole a Network panel. [1][11]
`CORS request did not succeed` může způsobit i DNS, timeout, odmítnuté spojení, TLS, mixed content nebo browser extension. [11]
- Zkontrolujte přesný frontend origin: schéma, host a port.
- Zkontrolujte `OPTIONS`, pokud existuje.
- Ověřte status a `Access-Control-Allow-*` preflight odpovědi.
- Ověřte, že i skutečná odpověď obsahuje požadované CORS headers.
- U cookies zkontrolujte `credentials`, `Access-Control-Allow-Credentials`, `SameSite` a `Secure`.
- Zkontrolujte redirecty, TLS, mixed content a síťové chyby.
- Porovnejte browser request s fungujícím curl nebo API-tool requestem.
Nebezpečné CORS konfigurace
Slepé vracení příchozího `Origin` je nebezpečné, pokud requesty s credentials vracejí citlivá data. Útočník může ovládat vlastní origin a pokusit se číst odpovědi v kontextu přihlášeného uživatele. [16]
Příliš široké regulární výrazy a důvěra všem subdoménám jsou také rizikové, pokud lze některou subdoménu převzít. OWASP doporučuje přesný allowlist. [16][17]
`Access-Control-Allow-Origin: *` není automaticky zranitelnost u zcela veřejných dat bez credentials. Problémem je politika širší, než je skutečně potřeba. [8][17]
Výkon: cena preflightu a `Access-Control-Max-Age`
Preflight přidává před skutečný request další `OPTIONS` a může zvýšit latenci. Jeho výsledek lze cacheovat pomocí `Access-Control-Max-Age` v samostatné preflight cache. [5][10]
MDN uvádí bez headeru výchozí hodnotu 5 sekund. Prohlížeče mají vlastní maxima. Firefox omezuje na 86400 sekund a Chromium od verze 76 na 7200 sekund. [10]
Stabilní politiky lze cacheovat, ale ne tak dlouho, aby bylo obtížné rychle odvolat omylem příliš široké povolení.
Praktická konfigurace a checklist nasazení
Nejprve rozhodněte, zda endpoint má být vůbec dostupný cross-origin. Pokud ne, CORS headers nepřidávejte. Pokud ano, definujte allowlist originů, potřebné metody a headers a potřebu credentials. [14][17]
U dynamického allowlistu vraťte jen ověřený origin a přidejte `Vary: Origin`. S credentials používejte konkrétní origin a `Access-Control-Allow-Credentials: true`. Frontend toto serverové rozhodnutí nemůže nahradit. [1][2]
Po nasazení otestujte jednoduchý request, preflight, scénář s cookie, nepovolený origin a skutečné chyby v DevTools.
- Je cross-origin přístup opravdu nutný?
- Které přesné originy mají být povoleny?
- Jsou potřeba credentials?
- Které metody a request headers jsou potřeba?
- Dostane se `OPTIONS` do aplikace a odpoví správně?
- Obsahuje skutečná odpověď správné CORS headers?
- Obsahují dynamické odpovědi `Vary: Origin`?
- Vyžaduje endpoint stále běžnou autentizaci a autorizaci?
- Byla politika otestována s povoleným i nepovoleným originem?

