CORS uitgelegd: waarom cross-origin requests worden geblokkeerd
Kort antwoord
CORS (Cross-Origin Resource Sharing) is een beveiligingsmechanisme van de browser, geen functie van een server of firewall. Het blokkeert dat een webpagina op de ene origin requests doet naar een andere origin, tenzij die andere origin dit expliciet toestaat via response headers. Werkt je frontend prima als je de API test met curl, maar loopt het vast in de browserconsole? Dan is dit vrijwel altijd de reden.
Wat telt als een "origin"
Een origin is de combinatie van scheme, domein en poort waarvan een pagina is geladen. https://app.example.com en https://api.example.com zijn verschillende origins, ook al delen ze hetzelfde hoofddomein. Hetzelfde geldt voor http://example.com en https://example.com, omdat het scheme verschilt. Zodra JavaScript op de ene origin een URL op een andere origin probeert aan te roepen, is dat een cross-origin request, en bemoeit de browser zich ermee.
Waarom dit bestaat
Browsers hanteren standaard een same-origin policy: een script mag vrij terugroepen naar de origin waar het vandaan komt, maar een andere origin aanroepen is beperkt, tenzij die origin aangeeft dat het oké is. De reden hiervoor is bescherming van de gebruiker, niet van de server die het verzoek doet. Zonder deze beperking zou een kwaadwillende site die je bezoekt stiekem JavaScript kunnen draaien dat requests stuurt naar je bank, je e-mailprovider of elke andere site waar je bent ingelogd, met gebruik van de sessiecookies die je browser al voor die sites bewaart. CORS voorkomt dat een pagina die je niet bewust vertrouwt, ongemerkt namens jou handelt tegenover een site die je wél vertrouwt.
Het symptoom waar ontwikkelaars steeds weer tegenaan lopen
Dit is het patroon waar bijna iedereen vroeg of laat tegenaan loopt: een frontend op het ene domein roept een API aan op een ander domein. De API zelf werkt prima als je die rechtstreeks test, met curl, Postman of een REST-client, want geen van die tools is een browser en geen ervan handhaaft CORS. Maar exact dezelfde request vanuit JavaScript in de browser mislukt, en de console van de browser toont een CORS-fout in plaats van een netwerkfout. De request bereikte de server vaak gewoon, en de server gaf vaak gewoon antwoord. De browser weigerde alleen om dat antwoord aan de JavaScript van de pagina te geven, omdat de server van de API nooit de headers terugstuurde die aangeven: "deze origin mag mijn response lezen".
De oplossing, conceptueel
Je lost CORS op bij de server die wordt aangeroepen, niet bij de frontend die het verzoek doet. De API moet een Access-Control-Allow-Origin header meesturen in de response, met daarin de origin (of origins) die de response mogen lezen. Afhankelijk van wat de request moet doen, regelen bijbehorende headers welke HTTP-methoden zijn toegestaan (Access-Control-Allow-Methods), welke custom headers verstuurd mogen worden (Access-Control-Allow-Headers), en of credentials zoals cookies zijn toegestaan (Access-Control-Allow-Credentials). Bij alles wat verder gaat dan een simpele GET-request stuurt de browser meestal eerst een preflight OPTIONS-request, om bij de server te checken of het eigenlijke verzoek is toegestaan voordat hij het echt verstuurt.
Wat CORS niet beschermt
Het loont om precies te zijn over wat CORS eigenlijk is: een regel die door browsers wordt afgedwongen, voor browsers. Het houdt geen enkel script, server-naar-server-integratie of tool zoals curl tegen om een API rechtstreeks aan te roepen, want geen daarvan handhaaft sowieso de same-origin policy. Een API die publiek bereikbaar moet zijn, is met of zonder CORS-headers even goed bereikbaar vanuit alles wat geen browser is die een cross-origin response leest. Moet een API beperken wie er überhaupt mag aanroepen? Dan is dat een taak voor authenticatie, API keys of IP-allowlisting, niet voor CORS.