Gidsen

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:

URLOrigin
https://mijnapp.nl/dashboardhttps://mijnapp.nl
https://api.mijnapp.nl/usershttps://api.mijnapp.nl (ander subdomein, dus andere origin)
http://localhost:5173http://localhost:5173
http://localhost:3000http://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 bevatWat er mis is
No 'Access-Control-Allow-Origin' header is presentDe 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 originDe server staat een andere origin toe dan de jouwe
Response to preflight request doesn't pass access control checkHet 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 allowedDe 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.nl
  • Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
  • Access-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.nl roept de API aan op https://api.mijnapp.nl en krijgt “Response to preflight request doesn't pass access control check”. Configureer CORS op de API zodat alleen https://mijnapp.nl en 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" in fetch, of withCredentials: true in axios.
  • De server stuurt Access-Control-Allow-Credentials: true.
  • De Access-Control-Allow-Origin van 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.proxy kan /api doorsturen naar je backend op een andere poort, zodat de browser maar met één origin praat.
  • In productie stuur je /api naar 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" in fetch. 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

SituatieOplossing
Frontend roept een API van derden aan met een geheime sleutelVerplaats de aanroep naar de backend
Werkt in de preview, faalt op het gepubliceerde domeinVoeg 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 DELETEPreflight niet afgehandeld; voeg OPTIONS-afhandeling en toegestane headers toe
Fout verscheen na het toevoegen van loginCredentials vereisen een exacte origin en Allow-Credentials
“CORS-fout” maar het tabblad Netwerk toont 500Los eerst de serverfout op
API-URL hard gecodeerd op localhostGebruik 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-Origin met 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.

Mythex · Pricing · Bouw alles met AI · Website- en app-templates · Mythex vergeleken · Woordenlijst voor apps bouwen met AI · Gratis tools voor apps bouwen met AI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)