Naar de inhoud
Datadenkt

Gereedschap  /  Beveiliging

CORS, en waarom je het steeds tegenkomt

De foutmelding die iedereen wegklikt met een regel van internet, en wat die regel echt uitzet.

Op enig moment krijg je deze in je console: has been blocked by CORS policy, no Access-Control-Allow-Origin header is present. Je vraagt het aan een AI, die zegt zet de origin op een sterretje, het werkt weer, en je gaat door. Dat sterretje is precies het probleem, want CORS is geen slot dat jou tegenhoudt maar een sleutel die jij uitdeelt.

Meenemen naar je editor

Deze handleiding is geschreven om als werkopdracht te dienen. Neem hem in zijn geheel mee naar Cursor, Claude Code of waar je ook bouwt, en werk hem fase voor fase af.

Waar het misgaat

CORS beschermt je server nietDie regel bestaat alleen in de browser. Curl, Postman en elk script trekken zich er niets van aan.
Het verzoek is meestal al uitgevoerdDe browser blokkeert dat jij het antwoord leest, niet dat het gebeurde. Je database is dus al bevraagd.
Sterretje plus credentials mag nietBrowsers weigeren die combinatie. Je zegt dan dat elke site ter wereld verzoeken mag doen mét jouw cookies.
Het is geen vervanging voor inloggenGeeft je endpoint zonder controle gegevens terug, dan is hij open, hoe streng je CORS ook instelt.

Fase 0Begrijpen wat je uitzet

Twee alinea's, en daarna snap je waarom het sterretje geen oplossing is.

Browsers hebben een oude, harde regel: JavaScript op site A mag niet zomaar de antwoorden lezen van site B. Zonder die regel zou een willekeurige site die je in een ander tabblad opent jouw ingelogde webmail kunnen uitlezen, want je cookies gaan gewoon mee.

CORS is de uitzondering daarop. Het is de manier waarop server B tegen de browser zegt: van site A mag je dit wél lezen. Zet je daar een sterretje neer, dan zeg je: van iedereen.

Fase 1Het sterretje vervangen

Een vaste lijst met adressen die jij kent, en geen regex waar per ongeluk van alles doorheen glipt.

CORS in Express, zoals het hoort
import cors from 'cors';

// Een vaste lijst met adressen die jij kent. Geen sterretje, en geen regex
// waar per ongeluk mijnsite.nl.kwaadaardig.com doorheen glipt.
const TOEGESTAAN = [
  'https://jouwsite.nl',
  'https://www.jouwsite.nl',
  ...(process.env.NODE_ENV === 'development' ? ['http://localhost:5173'] : [])
];

app.use(cors({
  origin(herkomst, klaar) {
    // Geen herkomst betekent: geen browser (curl, een server, een mobiele app).
    // Die laat je door of niet, maar CORS is daar sowieso je beveiliging niet.
    if (!herkomst) return klaar(null, true);
    if (TOEGESTAAN.includes(herkomst)) return klaar(null, true);
    return klaar(new Error('niet toegestaan door CORS'));
  },
  credentials: true,          // nodig als je cookies meestuurt
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  maxAge: 86400               // scheelt een preflight-verzoek per dag
}));

Fase 2Als de fout blijft

Vier symptomen en waar je fout dan echt zit.

Wat je zietWat het meestal is
Fout verdwijnt niet na aanpassenJe preflight wordt gecachet, of je bewerkt de verkeerde omgeving
Werkt lokaal, niet livelocalhost staat in je lijst en je echte domein niet, of www ontbreekt
Alleen bij POST, niet bij GETDe preflight met OPTIONS wordt niet beantwoord door je router
Cookies komen niet aancredentials ontbreekt aan de fetch-kant, of SameSite staat op Lax

Fase 3Controleren

Bewijs voordat je zegt dat het klaar is. Twee commando's, geen browser nodig.

Wat je server echt terugstuurt
# Een toegestaan adres: hier hoort je eigen domein terug te komen.
curl -s -I -H "Origin: https://jouwsite.nl" https://api.jouwsite.nl/iets \
  | grep -i access-control-allow-origin

# Een adres dat er niet in staat: hier hoort GEEN header terug te komen.
curl -s -I -H "Origin: https://kwaadaardig.example" https://api.jouwsite.nl/iets \
  | grep -i access-control-allow-origin

# De preflight, voor een POST met een eigen header:
curl -s -I -X OPTIONS \
  -H "Origin: https://jouwsite.nl" \
  -H "Access-Control-Request-Method: POST" \
  https://api.jouwsite.nl/iets
  • Je eigen domein krijgt de header terugZonder www én met www, want dat zijn voor de browser twee verschillende adressen.
  • Een onbekend adres krijgt géén header terugKrijg je hem wel, of staat er een sterretje, dan staat je lijst nog niet aan.
  • De preflight geeft een 200 of 204Een 404 op OPTIONS betekent dat je router de preflight niet afhandelt, en dan faalt elke POST.
  • Er staat nergens meer origin: '*' in je codeZoek er letterlijk op, ook in je serverless-functies en je middleware.
Nog een keer, nu je weet wat erin staat

Alles hierboven in één keer, inclusief de fases, de SQL en de controlestap.

Meer uit beveiliging

Werkt dit, maar wil je het niet zelf doen?

Dit blad is gratis en blijft gratis. Wil je dat het bij jou automatisch draait in plaats van met de hand, dan bouw ik dat. Beschrijf in vijf vragen wat er vastloopt en stuur het adres van je app mee; binnen één werkdag heb je mijn antwoord op papier.

Vertel wat er vastloopt