CORS-fouten oplossen (en waarom ze ontstaan)
Een CORS-fout betekent dat je webpagina een antwoord probeerde te lezen van een andere origin (een ander domein, poort of protocol), en dat die server geen headers stuurde die dat toestaan. Browsers blokkeren het antwoord om gebruikers te beschermen. Je lost het op de server op, door een Access-Control-Allow-Origin-header terug te sturen voor de origin van je site en OPTIONS-preflight-verzoeken af te handelen, of je vermijdt het door het verzoek via je eigen backend op dezelfde origin te sturen.
Het is een van de verwarrendste fouten in webontwikkeling, omdat het verzoek vaak prima bij de server aankomt. Deze gids legt uit wat er gebeurt en hoe je elke variant oplost.
Wat “origin” betekent
Een origin is de combinatie van protocol, domein en poort:
| URL | Origin |
|---|---|
https://mijnapp.nl/dashboard | https://mijnapp.nl |
https://api.mijnapp.nl/users | https://api.mijnapp.nl (ander subdomein, dus andere origin) |
http://localhost:5173 | http://localhost:5173 |
http://localhost:3000 | http://localhost:3000 (andere poort, dus andere origin) |
Browsers volgen het same-origin-beleid: JavaScript op een pagina mag vrij antwoorden lezen van de eigen origin, maar niet van andere. CORS (Cross-Origin Resource Sharing) is het systeem waarmee een server zich opt-in: “antwoorden van mij mogen worden gelezen door pagina’s van deze origins.”
Twee gevolgen verrassen mensen:
- Het verzoek bereikt vaak de server. CORS blokkeert meestal dat jouw code het antwoord leest, niet dat de server het verzoek ontvangt. Een POST kan zijn opgeslagen, ook al zag je een fout.
- Alleen browsers handhaven het. curl, Postman en code aan de serverkant negeren CORS volledig. Dat “het werkt in Postman” dus niets zegt over CORS.
Stap 1: lees de exacte fout
Open de ontwikkelaarstools (klik met rechts → Inspecteren), ga naar de Console en lees het volledige bericht. Chrome’s formulering vertelt welk probleem je hebt:
| Bericht bevat | Wat er mis is |
|---|---|
No 'Access-Control-Allow-Origin' header is present | De server stuurde de header helemaal niet, of het verzoek mislukte voordat het je CORS-code bereikte (bijvoorbeeld een 404 of 500) |
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin | De server staat een andere origin toe dan de jouwe |
Response to preflight request doesn't pass access control check | Het OPTIONS-preflight-verzoek is mislukt |
...must not be the wildcard '*' when the request's credentials mode is 'include' | Je stuurt cookies, en de server antwoordde met * |
Request header field authorization is not allowed | De server heeft die header niet opgenomen in Access-Control-Allow-Headers |
Open daarna het tabblad Netwerk, zoek het mislukte verzoek (en eventuele OPTIONS-verzoeken ervoor), en kijk naar de statuscode en de antwoordheaders.
Controleer eerst de statuscode. Veel “CORS-fouten” zijn in feite een serverfout in vermomming. Als de server crasht of een 404 geeft, mist het foutantwoord vaak de CORS-headers, waardoor de browser CORS meldt in plaats van het echte probleem. Los de 500 of 404 op, en de CORS-fout verdwijnt mogelijk ook.
Stap 2: bepaal of je die API überhaupt vanuit de browser moet aanroepen
Vraag je voordat je headers toevoegt: moet de browser deze API rechtstreeks aanroepen?
- Een API van derden die een geheime sleutel nodig heeft (OpenAI, de geheime sleutel van Stripe, de meeste e-mailservices) mag nooit vanuit de browser worden aangeroepen. De sleutel zou zichtbaar zijn voor elke bezoeker. Roep hem aan vanuit je backend, dan verdwijnt het CORS-probleem, omdat verzoeken van server naar server niet onder CORS vallen. Zie hoe je API-sleutels veilig houdt.
- Een API van derden die je niet beheert en die geen CORS-headers stuurt, kun je niet vanuit jouw kant oplossen. Roep hem aan vanuit je backend.
- Je eigen API kun je oplossen door hem goed te configureren. Lees verder.
Stap 3: los het op op je server
De server moet de juiste headers toevoegen aan zijn antwoorden, ook aan foutantwoorden.
Voor het eigenlijke verzoek stuur je:
Access-Control-Allow-Origin: https://mijnapp.nl(de exacte origin van je frontend, zonder slash aan het eind)Vary: Origin, als de waarde afhangt van welke origin vraagt
Voor het preflight-OPTIONS-verzoek antwoord je met een successtatus (200 of 204) en:
Access-Control-Allow-Origin: https://mijnapp.nlAccess-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETEAccess-Control-Allow-Headers: Content-Type, Authorization(elke eigen header die je stuurt)- Optioneel
Access-Control-Max-Age: 600, zodat de browser het preflight een tijd cachet
De meeste frameworks hebben een pakket of instelling die dit doet. In Express het pakket cors; in FastAPI CORSMiddleware; in ASP.NET Core AddCors; in Spring @CrossOrigin of een CORS-configuratie. Een typische Express-opzet:
import cors from "cors";
app.use(cors({
origin: ["https://mijnapp.nl", "http://localhost:5173"],
credentials: true,
}));
Zet CORS-afhandeling vóór je routes en authenticatie, zodat preflight-verzoeken (die geen login bevatten) niet met een 401 worden geweigerd.
Met een AI-bouwer beschrijf je de opzet precies:
De frontend op
https://mijnapp.nlroept de API aan ophttps://api.mijnapp.nlen krijgt “Response to preflight request doesn't pass access control check”. Configureer CORS op de API zodat alleenhttps://mijnapp.nlen de preview-origin zijn toegestaan, sta GET, POST, PATCH, DELETE toe met de headers Content-Type en Authorization, handel OPTIONS af vóór de authenticatie, en voeg de CORS-headers ook toe aan foutantwoorden.
Stap 4: cookies en credentials afhandelen
Als je frontend cookies (bijvoorbeeld een loginsessie) naar een andere origin stuurt, moeten beide kanten het erover eens zijn:
- De frontend zet
credentials: "include"infetch, ofwithCredentials: truein axios. - De server stuurt
Access-Control-Allow-Credentials: true. - De
Access-Control-Allow-Originvan de server moet de exacte origin zijn, niet*.
Cookies over sites heen hebben ook SameSite=None; Secure nodig, en sommige browsers beperken cookies van derden verder. Dat is nog een reden om de aanpak uit Stap 5 te verkiezen.
Stap 5: vermijd CORS met een opzet op dezelfde origin
Vaak is de schoonste oplossing om helemaal geen verzoeken over origins heen te doen. Serveer de API vanaf dezelfde origin als de pagina, bijvoorbeeld op https://mijnapp.nl/api/...:
- In ontwikkeling kunnen de meeste devservers proxyen. Vite’s instelling
server.proxykan/apidoorsturen naar je backend op een andere poort, zodat de browser maar met één origin praat. - In productie stuur je
/apinaar de backend via de rewrite- of proxy-instellingen van je host, of serveer je frontend en API vanaf één server.
De frontend roept dan een relatief pad aan, zoals fetch("/api/orders"). Er is geen cross-origin-verzoek, geen preflight over origins heen, en geen cookieproblemen. Je hoeft ook geen API-domein hard te coderen dat verschilt tussen preview en productie.
Pas de frontend aan zodat hij de backend aanroept met relatieve /api-paden in plaats van een volledige URL, en stel een dev-proxy in zodat /api doorstuurt naar de API-server. Verwijder daarna alle CORS-instellingen die we niet meer nodig hebben.
Wat je beter niet doet
mode: "no-cors"infetch. Het omzeilt niets; het geeft je code een lege, onleesbare respons.- Browserextensies die CORS uitschakelen. Die veranderen alleen je eigen browser. Elke bezoeker ziet nog steeds de fout.
- Openbare “CORS-proxy”-diensten voor echte apps. Ze kunnen alles lezen en wijzigen wat erdoorheen gaat, en ze liggen er wel eens uit.
Access-Control-Allow-Origin: *op een API die met logins werkt. Het werkt sowieso niet met credentials, en het is de verkeerde standaard voor privédata.- Elke
Origin-header terugkaatsen met credentials toegestaan. Dat laat elke website verzoeken doen namens je gebruikers terwijl ze ingelogd zijn. Gebruik een allowlist.
Veelvoorkomende oorzaken in met AI gebouwde apps
| Situatie | Oplossing |
|---|---|
| Frontend roept een API van derden aan met een geheime sleutel | Verplaats de aanroep naar de backend |
| Werkt in de preview, faalt op het gepubliceerde domein | Voeg de gepubliceerde origin en de origin van het eigen domein toe aan de allowlist, of gebruik relatieve /api-paden |
| Fout alleen bij POST, PUT of DELETE | Preflight niet afgehandeld; voeg OPTIONS-afhandeling en toegestane headers toe |
| Fout verscheen na het toevoegen van login | Credentials vereisen een exacte origin en Allow-Credentials |
| “CORS-fout” maar het tabblad Netwerk toont 500 | Los eerst de serverfout op |
API-URL hard gecodeerd op localhost | Gebruik relatieve paden of een instelling per omgeving |
CORS in Mythex
Als een Mythex-project een website en een API-server heeft, draaien beide als diensten in hetzelfde project, en je kunt tussen ze wisselen in de preview. Vragen aan de agent om de frontend de backend te laten aanroepen via een relatief /api-pad, in ontwikkeling geproxied, voorkomt de meeste CORS-problemen. Heb je wel cross-origin-aanroepen nodig, geef de agent dan de exacte fout uit de console en de betrokken origins. Controleer het resultaat ook op de gepubliceerde app, omdat het adres daarvan verschilt van de preview. Zie Apps met meerdere diensten en API-backends in de documentatie, en onze gidsen over het debuggen van een met AI gebouwde app en frontend versus backend.
CORS-checklist
- De volledige consolefout en de statuscode van het mislukte verzoek gelezen
- Een verborgen 404 of 500 uitgesloten
- API’s met geheime sleutels worden vanuit de backend aangeroepen, niet vanuit de browser
- De server stuurt
Access-Control-Allow-Originmet exacte toegestane origins -
OPTIONS-preflight beantwoord met toegestane methoden en headers, vóór de authenticatie - CORS-headers aanwezig op foutantwoorden
- Credentials: exacte origin,
Allow-Credentials: true, juiste cookie-instellingen - Preview-, gepubliceerde en eigen-domein-origins zijn allemaal gedekt, of relatieve
/api-paden worden gebruikt - Geen
no-cors, openbare proxy’s of snelkoppelingen die elke origin terugkaatsen
Veelgestelde vragen
Wat betekent een CORS-fout?
Je pagina, geladen vanaf de ene origin, probeerde een antwoord te lezen van een andere origin, en de server van die andere origin stuurde geen headers die dat toestaan. De browser blokkeert dat je code het antwoord leest. De oplossing zit op de server, of in hoe het verzoek wordt gerouteerd, niet in de frontendcode.
Kan ik een CORS-fout vanuit de frontend oplossen?
Niet echt. Instellingen zoals mode: 'no-cors' verbergen alleen het antwoord voor je code. Je lost het op door de server de juiste Access-Control-Allow-Origin-header te laten sturen, of door de API aan te roepen vanuit je eigen backend of een proxy op dezelfde origin.
Is Access-Control-Allow-Origin: * veilig?
Het is prima voor openbare data die iedereen mag lezen, zoals een openbare API zonder login. Het kan niet met cookies of andere credentials worden gebruikt, en voor privé-API’s moet je de exacte toegestane origins opsommen.
Waarom werkt mijn verzoek in Postman of curl maar niet in de browser?
CORS wordt alleen door browsers afgedwongen. Tools als Postman en curl passen het niet toe, dus een verzoek kan daar slagen en toch worden geblokkeerd wanneer JavaScript van de pagina het doet.
Wat is een preflight-verzoek?
Voor verzoeken die niet simpel zijn, zoals een JSON-POST of een verzoek met een Authorization-header, stuurt de browser eerst een OPTIONS-verzoek om te vragen welke methoden en headers zijn toegestaan. Als dat OPTIONS-verzoek mislukt of de juiste headers mist, wordt het echte verzoek nooit verstuurd.