Een API kan bereikbaar zijn en toch vanuit een browser een CORS-fout geven. Lees hoe het same-originbeleid werkt, wanneer een browser een preflightverzoek verstuurt en welke serverheaders nodig zijn om een cross-origin response beschikbaar te maken.
Het same-originbeleid beperkt wat browsercode kan lezen
Browsers hanteren het same-originbeleid om te beperken hoe een script van de ene website gegevens van een andere origin kan benaderen. Een origin bestaat uit het schema, de hostnaam en de poort. Zo zijn https://app.example.nl en https://api.example.nl verschillende origins, net als http://app.example.nl en https://app.example.nl. Zelfs een ander poortnummer maakt een origin verschillend. Een pad, zoals /dashboard, verandert de origin niet.
Het beleid voorkomt niet dat iedere cross-origin aanvraag wordt verstuurd. Een browser kan bijvoorbeeld een afbeelding laden of een formulier naar een andere site versturen. De belangrijke beperking is dat scripts de response niet zomaar mogen uitlezen. Dat onderscheid verklaart waarom een verzoek in de netwerkmonitor als geslaagd kan verschijnen, terwijl JavaScript toch een CORS-fout meldt. De server heeft mogelijk een response teruggestuurd, maar de browser geeft die response niet vrij aan de aanvragende code.
CORS is het mechanisme waarmee een server de browser kan vertellen welke origins een response mogen lezen. Het is dus geen algemene blokkade op netwerkverkeer en ook geen regel die een API-server zelf afdwingt. De browser beoordeelt de CORS-headers op de response en past het same-originbeleid toe. Bij onderzoek helpt het daarom om vast te stellen of de aanvraag aankomt, wat de server antwoordt en of de browser die response aan het script beschikbaar stelt.
Wat CORS-headers op een API-response regelen
Cross-Origin Resource Sharing, afgekort CORS, is een afspraak tussen server en browser. De browser voegt bij een cross-origin aanvraag een Origin-header toe, bijvoorbeeld met de origin van de webapplicatie. De server kan vervolgens met Access-Control-Allow-Origin aangeven welke origin de response mag uitlezen. Bij een specifieke toegestane origin moet de waarde overeenkomen met de origin uit het verzoek, inclusief schema en eventuele poort. Een pad hoort er niet bij.
Een open endpoint zonder gebruikersgegevens kan soms de waarde * gebruiken. Daarmee wordt de response in beginsel beschikbaar voor scripts van iedere origin, maar niet voor aanvragen met browsercredentials zoals cookies. Als credentials nodig zijn, moet de server een concrete toegestane origin teruggeven en ook Access-Control-Allow-Credentials: true meesturen. De waarde van die header is geen algemene tekstuele true/false-instelling: bij ingeschakelde credentials is de toegestane waarde letterlijk true.
CORS-headers moeten terechtkomen op de response die de browser beoordeelt, ook wanneer de server een foutstatus retourneert. Als een reverse proxy, gateway of applicatie bij een fout een response zonder CORS-headers afgeeft, ziet de frontend mogelijk alleen een algemene CORS-melding en niet de onderliggende fout. Behandel headers daarom als onderdeel van de volledige responseketen. Controleer zowel succesvolle antwoorden als foutpaden, waaronder 401-, 403- en 500-responses.
Wanneer een browser een CORS-preflight uitvoert
Voor bepaalde cross-origin verzoeken stuurt de browser eerst een preflightverzoek. Dat is een afzonderlijk HTTP-verzoek met de methode OPTIONS, bedoeld om te controleren of de server de geplande aanvraag toestaat. De browser gebruikt dit onder meer wanneer de echte aanvraag een methode gebruikt zoals PUT of DELETE, wanneer JavaScript niet-standaardheaders toevoegt of wanneer de contenttype buiten de toegestane eenvoudige waarden valt. Een gewone aanvraag met Content-Type: application/json leidt daardoor vaak tot een preflight.
De preflight bevat Origin, Access-Control-Request-Method en, indien van toepassing, Access-Control-Request-Headers. De server moet daarop antwoorden met passende waarden in Access-Control-Allow-Origin, Access-Control-Allow-Methods en Access-Control-Allow-Headers. De browser vergelijkt die antwoorden met de geplande aanvraag. Als een gevraagde header ontbreekt of de methode niet is toegestaan, wordt de echte aanvraag niet verstuurd.
Een veelvoorkomende configuratiefout is dat middleware de methode OPTIONS niet afhandelt, of dat authenticatie de preflight afwijst voordat de CORS-laag reageert. Preflightverzoeken bevatten doorgaans geen normale applicatiegegevens waarmee een gebruiker kan worden geauthenticeerd. De server moet daarom de CORS-controle op de juiste plek in de middlewareketen uitvoeren. Dat betekent niet dat de daaropvolgende echte aanvraag anoniem mag zijn: die blijft afzonderlijk aan authenticatie en autorisatie onderworpen.
Welke headers nodig zijn voor preflight en echte aanvragen
De benodigde headers hangen af van wat de frontend werkelijk verstuurt. Voor een eenvoudige cross-origin aanvraag is vaak alleen Access-Control-Allow-Origin nodig op de response. Voor een preflight moet de server daarnaast de gevraagde methode en eventuele aangepaste requestheaders toestaan. Een API die JSON accepteert en een autorisatieheader gebruikt, kan bijvoorbeeld Content-Type en Authorization moeten opnemen in Access-Control-Allow-Headers. De browser vraagt alleen de relevante headers op in Access-Control-Request-Headers.
Access-Control-Allow-Methods beschrijft welke methoden de browser voor cross-origin aanvragen mag gebruiken. Het is niet hetzelfde als de methoden die de applicatie functioneel ondersteunt: de router kan een methode nog steeds afwijzen. Ook Access-Control-Expose-Headers heeft een eigen taak. Die header maakt geselecteerde responseheaders leesbaar voor JavaScript; zonder die expliciete toestemming kan code niet zomaar iedere header uit de response bekijken.
Vermijd brede lijsten die niet aansluiten op de daadwerkelijke frontend. Een configuratie met iedere methode en iedere header maakt fouten minder zichtbaar en vergroot de toegestane browserinterface. Stel de lijst samen op basis van de gebruikte endpoints en requestvormen. Test vervolgens met dezelfde methode en headers als de applicatie, want een controle met alleen een eenvoudige GET bewijst niet dat een JSON-POST of een aanvraag met een autorisatieheader goed is ingericht.
Credentials, cookies en de beperkingen van een wildcard
Een browseraanvraag kan credentials gebruiken, zoals cookies of HTTP-authenticatie. Bij de Fetch API vraagt de frontend dat expliciet aan met een geschikte instelling voor credentials; bij XMLHttpRequest bestaat daarvoor een overeenkomstige optie. De server moet daarnaast Access-Control-Allow-Credentials: true teruggeven. Als één kant van die combinatie ontbreekt, kan de browser de response weigeren beschikbaar te stellen, ook als de overige CORS-headers correct lijken.
Met credentials is Access-Control-Allow-Origin: * niet toegestaan. De server moet de concrete origin terugsturen die toegang krijgt. Dat vraagt om een gecontroleerde allowlist: vergelijk de ontvangen Origin met bekende waarden en geef alleen bij een match die origin terug. Echo niet zonder controle iedere ontvangen waarde. Dat zou de browsertoegang voor willekeurige websites openzetten, terwijl ontwikkelaars vaak denken dat ze een vaste lijst gebruiken.
Cookies hebben bovendien browserregels die losstaan van CORS. De attributen SameSite, Secure en het domein bepalen mede of een cookie wordt meegestuurd. Moderne browsers beperken ook third-party cookies. Een juiste CORS-configuratie garandeert dus niet dat een cookie meegaat. Omgekeerd is een cookie die wel wordt meegestuurd geen bewijs dat de response mag worden uitgelezen. Onderzoek beide lagen apart: controleer de requestinstellingen, cookie-attributen, CORS-respons en de relevante browserprivacyregels.
CORS-fouten vinden in browsertools en serverlogs
Een browsermelding als CORS policy blocked the request beschrijft vaak het gevolg, niet de oorzaak. Open de netwerkmonitor en bekijk zowel het preflightverzoek als de echte aanvraag. Als alleen OPTIONS zichtbaar is, heeft de preflight waarschijnlijk niet de vereiste toestemming gekregen. Controleer de statuscode, de requestheaders en de volledige responseheaders. Als de echte aanvraag wel is verstuurd, maar JavaScript de response niet kan lezen, ontbreekt mogelijk een CORS-header op die response of op een foutrespons.
Vergelijk de waarde van Origin exact met Access-Control-Allow-Origin. Veelvoorkomende afwijkingen zijn een verkeerd schema, een vergeten poort, een trailing slash of een header die door meerdere lagen dubbel wordt toegevoegd. Let ook op redirects: een redirect naar een andere host of een loginpagina kan een andere response opleveren dan verwacht. Een reverse proxy kan bovendien de OPTIONS-aanvraag blokkeren of headers overschrijven voordat de response de browser bereikt.
Gebruik serverlogs om vast te stellen of de aanvraag de applicatie of gateway heeft bereikt; browsercode alleen kan dat onderscheid niet altijd maken. Test een vergelijkbare aanvraag met een HTTP-client, maar interpreteer het resultaat voorzichtig: een terminalclient handhaaft het same-originbeleid niet. Een geslaagde test met zo’n client bewijst dat de server bereikbaar is, niet dat CORS goed staat. Neem bij diagnose ook de statuscode en headers van foutpaden mee, want juist daar verdwijnt CORS-configuratie geregeld door afwijkende middleware.
Origin allowlists en fouten bij dynamische configuratie
Veel applicaties ondersteunen meerdere frontendomgevingen, zoals productie, acceptatie en lokale ontwikkeling. Een allowlist maakt expliciet welke origins een browserresponse mogen lezen. Houd die lijst per omgeving beheersbaar en neem alleen origins op die daadwerkelijk bij het systeem horen. Een veelgemaakte fout is een te brede wildcard op subdomeinen, of een controle die simpelweg test of een origintekst eindigt op een bepaalde domeinnaam. Zo’n vergelijking kan ook een aanvallersdomein accepteren dat dezelfde tekenreeks als achtervoegsel bevat.
Vergelijk origins als gestructureerde waarden: schema, host en poort moeten overeenkomen met een toegestane combinatie. Normaliseer invoer waar nodig, maar maak de vergelijking niet ruimer dan het ontwerp vereist. Als de server de ontvangen origin dynamisch terugstuurt, moet dat alleen gebeuren nadat de waarde de allowlistcontrole heeft doorstaan. Bij een match kan de server Vary: Origin toevoegen, zodat caches responses voor verschillende origins niet onbedoeld als één variant behandelen.
Een configuratie kan in ontwikkeling werken en in productie falen doordat de lokale server een andere poort gebruikt, HTTPS ontbreekt of een proxy het schema anders doorgeeft. Leg daarom de toegestane origins vast in de configuratie van de omgeving en controleer welke origin de browser werkelijk verstuurt. Vertrouw niet blind op een proxyheader die de client zelf kan beïnvloeden; gebruik alleen headers die door een vertrouwde proxy worden gezet en correct worden afgeschermd. Zo blijft de CORS-regel voorspelbaar bij deployments en domeinwijzigingen.
CORS is geen authenticatie of autorisatie
CORS bepaalt of browsercode een cross-origin response mag lezen. Het identificeert geen gebruiker, controleert geen accountrechten en verhindert niet dat een aanvaller rechtstreeks HTTP-verzoeken naar een endpoint stuurt. Een script buiten een browser, een backendserver of een aangepaste client hoeft het same-originbeleid niet te volgen. Daarom mag een API nooit aannemen dat een aanvraag veilig is omdat de browser CORS-regels toepast of omdat de requestheader Origin een bekende waarde bevat.
Authenticatie bepaalt wie een aanvraag doet, bijvoorbeeld met een sessiecookie of een token. Autorisatie bepaalt vervolgens welke bewerking die identiteit mag uitvoeren. Die controles moeten op de server plaatsvinden voor iedere beschermde actie. CORS kan wel de browsertoegang tot responses beperken, maar is geen vervanging voor endpointbeveiliging. Een endpoint dat gevoelige gegevens terugstuurt zonder server-side toegangscontrole blijft kwetsbaar, ongeacht welke origins de CORS-configuratie noemt.
Cookies brengen daarnaast een afzonderlijk risico mee: cross-site request forgery. Een browser kan in bepaalde situaties cookies automatisch meesturen bij een aanvraag die door een andere website wordt gestart. CORS voorkomt niet noodzakelijk dat zo’n aanvraag de server bereikt. Beoordeel daarom CSRF-bescherming op basis van het authenticatiemodel, cookie-instellingen, CSRF-tokens en eventueel controles op de origin van muterende requests. Houd die beveiligingsmaatregelen gescheiden van de vraag of JavaScript een response mag uitlezen; ze beschermen tegen andere dreigingen en moeten elk bewust worden ingericht.
CORS achter API-gateways, proxies en caches
In een productieomgeving kan een response door meerdere lagen gaan: een CDN, load balancer, API-gateway, reverse proxy en applicatieserver. CORS-headers kunnen op één of meer van die lagen worden toegevoegd. Wanneer zowel de gateway als de applicatie Access-Control-Allow-Origin instellen, kan de browser een dubbele of ongeldige header ontvangen. Kies daarom een duidelijke eigenaar voor CORS-beleid en controleer de uiteindelijke response zoals die de browser bereikt, niet alleen de instellingen in één component.
Ook caches kunnen originvarianten door elkaar halen. Als een server dynamisch de toegestane origin terugstuurt, moet een gedeelde cache weten dat de response afhangt van de requestheader Origin. De response hoort dan doorgaans Vary: Origin te bevatten. Zonder die instructie kan een cache een response met de CORS-header voor de ene website hergebruiken voor een andere. De cache kan bovendien de preflightrespons opslaan; Access-Control-Max-Age beïnvloedt hoelang de browser een preflightresultaat mag hergebruiken.
Een hogere maximale leeftijd vermindert herhaalde OPTIONS-aanvragen, maar maakt wijzigingen in beleid niet onmiddellijk zichtbaar bij clients die een oud resultaat hebben gecachet. Stel die waarde af op de gewenste balans tussen minder netwerkverkeer en een beheersbare beleidswijziging. Controleer ook dat gateways OPTIONS niet naar een andere route sturen dan bedoeld en dat foutresponses dezelfde CORS-behandeling krijgen. Deze ketencontrole voorkomt dat een correcte applicatieconfiguratie door infrastructuurgedrag wordt ondermijnd.
Wanneer een proxy de CORS-architectuur kan veranderen
Een ontwikkelserver of backend kan een proxy aanbieden die aanvragen van de frontend doorstuurt naar een externe API. De browser ziet dan vaak één origin voor de webapp en de proxy, waardoor de browser geen cross-origin toegang tot die externe API hoeft te beoordelen. Dit kan lokale ontwikkeling vereenvoudigen en voorkomt dat frontendcode rechtstreeks afhankelijk is van de CORS-configuratie van een derde partij. De proxy wordt daarmee wel een onderdeel van de applicatiearchitectuur, niet slechts een browserinstelling.
Een server-side proxy moet zorgvuldig omgaan met doorstuurregels. Beperk welke hosts bereikbaar zijn, welke paden mogen worden doorgestuurd en welke requestheaders worden overgenomen. Een onbeperkte proxy kan misbruikt worden om interne diensten te bereiken of als open relay te functioneren. Verwijder of beheer ook gevoelige headers op de servergrens; browsercode mag niet zomaar servercredentials ontvangen die nodig zijn om externe API’s te benaderen. Log voldoende om fouten te onderzoeken, maar voorkom dat tokens of persoonsgegevens onnodig in logs terechtkomen.
Een proxy lost evenmin authenticatie, autorisatie of de beschikbaarheid van de upstream API op. De server moet bepalen welke gebruiker een proxyaanvraag mag doen en welke gegevens mogen worden teruggegeven. Houd rekening met caching, time-outs, foutafhandeling en de herkomst van doorgestuurde headers. Voor een publieke API die door verschillende websites rechtstreeks vanuit browsers wordt gebruikt, blijft correcte CORS-configuratie aan de API-kant meestal relevant. De keuze tussen browserrechtstreeks en proxyverkeer bepaalt dus waar beleid en operationele verantwoordelijkheid komen te liggen.
Veelgestelde vragen
Hoe lang wordt een CORS-preflightresultaat door de browser onthouden?
Een browser kan een geslaagde preflight tijdelijk onthouden als de server de header Access-Control-Max-Age meestuurt. De waarde geeft aan hoeveel seconden de toestemming geldig mag blijven, zodat niet voor iedere vergelijkbare aanvraag opnieuw een OPTIONS-verzoek nodig is. Browsers kunnen zelf een maximum hanteren en de opgegeven duur inkorten. De instelling versnelt herhaalde verzoeken, maar verandert niet welke origins, methoden of headers zijn toegestaan.
- Controleer de ingestelde duur tijdens het testen; een oude toestemming kan wijzigingen tijdelijk verhullen.
- Gebruik een passende, beperkte cacheduur wanneer je CORS-regels regelmatig wijzigt.
Hoe gebruik ik een externe API als die mijn website niet toestaat?
Als je de CORS-instellingen van een externe API niet kunt aanpassen, kun je de API doorgaans via je eigen backend benaderen en het resultaat gecontroleerd doorgeven aan je frontend. De browser maakt dan een verzoek aan je eigen server, en die server communiceert vervolgens met de externe API. Dit wordt vaak een backendproxy genoemd. Bewaar eventuele API-sleutels op de server en geef niet automatisch iedere inkomende aanvraag toegang tot de externe dienst.
- Beperk welke externe routes en bewerkingen de proxy mag uitvoeren.
- Controleer gebruikersrechten en valideer invoer voordat je een verzoek doorstuurt.
- Houd rekening met limieten en voorwaarden van de externe API.
Geldt CORS ook voor afbeeldingen en webfonts?
CORS kan ook een rol spelen bij afbeeldingen en webfonts, maar het effect hangt af van hoe de browser de bestanden gebruikt. Een afbeelding van een andere origin kan vaak gewoon op een pagina worden getoond. Zodra JavaScript die afbeelding bijvoorbeeld in een canvas probeert uit te lezen, kan de browser de toegang blokkeren tenzij de afbeeldingsserver passende CORS-toestemming geeft. Webfonts die van een andere origin worden geladen, vereisen doorgaans eveneens een geschikte serverconfiguratie.
- Controleer de responseheaders op de server die het bestand levert.
- Test het daadwerkelijke gebruik, zoals canvasbewerking, niet alleen of het bestand zichtbaar laadt.
Geldt CORS voor mobiele apps en WebSockets?
CORS wordt vooral door browsers toegepast op webpagina’s en geldt niet op dezelfde manier voor alle mobiele apps of WebSocketverbindingen. Een native mobiele app die zelf HTTP-verzoeken verstuurt, valt doorgaans niet onder het same-originbeleid van een browser. Een mobiele app met een ingebouwde webview kan dat browsergedrag wel tegenkomen. Voor WebSockets gelden aparte regels: browsers kunnen een Origin meesturen, maar de server moet die zelf beoordelen.
- Beveilig mobiele API’s altijd met server-side authenticatie en autorisatie.
- Sta bij WebSockets alleen bedoelde origins toe en vertrouw Origin niet als bewijs van identiteit.
Kan ik CORS oplossen door de browserbeveiliging uit te schakelen?
Het uitschakelen van browserbeveiliging is geen veilige oplossing voor een CORS-probleem en maakt normaal gebruik van de website niet betrouwbaar. Een browser die met uitgeschakelde beveiliging draait, kan andere websites toegang geven tot gegevens die anders beschermd zijn. Bovendien verandert die instelling niets aan de ervaring van je bezoekers. Los het probleem op in de serverconfiguratie of gebruik tijdens lokale ontwikkeling een proxy die verzoeken via je ontwikkelserver laat lopen.
- Gebruik een ontwikkelproxy alleen voor de ontwikkelomgeving en configureer productie afzonderlijk.
- Pas CORS aan op de server die de response levert, als je die beheert.
- Schakel browserbeveiliging niet uit voor dagelijks browsen of voor accounts met gevoelige gegevens.