Ga naar hoofdinhoud

Cookies, sessions & CORS

HTTP is stateless: de server onthoudt tussen twee requests niets over jou. Toch blijf je ingelogd terwijl je van pagina naar pagina klikt. Deze pagina legt uit hoe dat kan, en waarom precies datzelfde mechanisme een beveiligingsregel nodig maakt die je bij je eerste fetch() naar een andere server tegen de muur laat lopen.

Stateless: elk request begint van nul​

Stel je twee requests voor, een halve seconde na elkaar:

GET /profiel HTTP/1.1 ← wie ben jij?
GET /bestellingen HTTP/1.1 ← en wie ben jij?

De server heeft geen geheugen van het eerste wanneer het tweede binnenkomt. Dat is bewust: het maakt servers eenvoudig en schaalbaar. Een verzoek kan door de ene machine behandeld worden en het volgende door een andere, zonder dat iemand iets moet doorgeven.

Het gevolg: wie je bent, moet bij elk request opnieuw blijken. Daarvoor bestaan twee gebruikelijke oplossingen: een cookie, of een token in de Authorization-header.

Cookies​

Een cookie is een klein stukje tekst dat de server aan de browser geeft en dat de browser daarna automatisch terugstuurt bij elk volgend request naar diezelfde site.

1. Je logt in
POST /login -> gebruiker + wachtwoord

2. De server antwoordt en geeft een cookie mee
HTTP/1.1 200 OK
Set-Cookie: session=a3f9c1; Path=/; HttpOnly; Secure; SameSite=Lax

3. Elk volgend request van je browser draagt het cookie
GET /profiel HTTP/1.1
Cookie: session=a3f9c1

De browser doet stap 3 helemaal zelf. Jij hoeft daar in je code niets voor te doen, en precies dat automatisme is later de kern van het CORS-verhaal.

De attributen die ertoe doen​

AttribuutWat het doetWaarom het belangrijk is
HttpOnlyJavaScript kan het cookie niet lezenBeperkt de schade als er kwaadaardige code op je pagina belandt
SecureWordt enkel over HTTPS verstuurdAnders staat je sessie leesbaar op de lijn
SameSite=Lax / StrictBeperkt meesturen bij verzoeken vanaf andere sitesBeschermt tegen verzoeken die een andere site in jouw naam doet
Max-Age / ExpiresHoelang het cookie geldig blijftZonder deze verdwijnt het bij het sluiten van de browser
Path / DomainVoor welke paden en domeinen het meegaatBeperkt de verspreiding van je sessie
Bekijk je eigen cookies

DevTools → Application → Cookies. Je ziet per site de naam, de waarde en de attributen. Zoek er een sessiecookie met HttpOnly aangevinkt: die kan je met JavaScript niet uitlezen, ook niet in je eigen console.

Sessions​

Een cookie hoeft je gegevens niet te bevatten, en dat is meestal ook niet de bedoeling. Bij een session bevat het cookie enkel een onvoorspelbaar sessie-id; alles wat daarbij hoort, blijft op de server.

browser server
Cookie: session=a3f9c1 ──► sessie a3f9c1 = gebruiker 42, ingelogd om 09:14, rol: student
Session met server-opslagToken (bv. JWT)
Wat de client bijhoudtEnkel een idDe gegevens zelf, ondertekend
Waar de gegevens staanOp de serverIn het token
Ongeldig makenWissen op de server, meteen wegMoeilijker: het token blijft geldig tot het vervalt
Meegestuurd viaCookie-header, automatischAuthorization-header, door jouw code
Conceptueel niveau

Je hoeft dit nu niet zelf te bouwen. Wat je moet kunnen uitleggen: HTTP onthoudt niets, dus elk request draagt zelf het bewijs van wie je bent, via een cookie of via een token.

Same-Origin Policy​

Nu het gevolg van dat automatisme. Omdat je browser cookies vanzelf meestuurt, zou elke website die je bezoekt in jouw naam gegevens kunnen opvragen bij je bank, je mailbox of je schoolplatform, en het antwoord uitlezen. Daarom leggen browsers zichzelf een grens op: de Same-Origin Policy.

Een origin bestaat uit drie delen. Alle drie moeten gelijk zijn.

https://app.voorbeeld.be:443
↑ ↑ ↑
scheme host poort

Vergeleken met https://app.voorbeeld.be:

URLZelfde origin?Waarom
https://app.voorbeeld.be/api/v2JaEnkel het pad verschilt, en dat telt niet mee
http://app.voorbeeld.beNeeAnder scheme
https://api.voorbeeld.beNeeAndere host, ook al is het hetzelfde domein
https://app.voorbeeld.be:8443NeeAndere poort

De regel: JavaScript op de ene origin mag standaard de response van een andere origin niet lezen.

Dit is een browserregel, geen serverregel

Precies daarom werkt curl https://api.voorbeeld.be/producten altijd, terwijl dezelfde aanroep vanuit je pagina geblokkeerd wordt. De Same-Origin Policy beschermt de gebruiker in zijn browser, niet de server.

CORS: de server geeft toestemming​

CORS (Cross-Origin Resource Sharing) is de manier waarop een server zegt: deze andere origin mag mijn antwoord wel lezen. Dat gebeurt met één response header:

Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Origin: *

Klik door wat er gebeurt bij een cross-origin verzoek:

http://localhost:5173 -> https://api.voorbeeld.be
Jouw JavaScript

Stap 1. Je code stuurt een fetch()

Een pagina op de ene origin vraagt data op bij een andere.

Je pagina draait op http://localhost:5173 en wil gegevens ophalen bij https://api.voorbeeld.be. Scheme, host en poort verschillen alle drie, dus dit is een cross-origin verzoek. Voor jouw code ziet het er nochtans uit als elk ander verzoek.

fetch('https://api.voorbeeld.be/producten')
1 / 7

De headers op een rij​

HeaderWie stuurt hemWat hij zegt
OriginBrowser, automatischVanaf welke site het verzoek komt
Access-Control-Allow-OriginServerWelke origin de response mag lezen (* of één adres)
Access-Control-Allow-MethodsServer, bij preflightWelke methoden toegelaten zijn
Access-Control-Allow-HeadersServer, bij preflightWelke eigen headers je mag meesturen
Access-Control-Allow-CredentialsServerOf cookies meegestuurd mogen worden
Access-Control-Max-AgeServerHoelang het preflight-antwoord hergebruikt mag worden
* en cookies gaan niet samen

Met Access-Control-Allow-Origin: * mag iedereen lezen, maar dan weigert de browser cookies mee te sturen. Wil je een sessie over origins heen, dan moet de server één concrete origin noemen én Access-Control-Allow-Credentials: true zetten, en jij credentials: 'include' meegeven aan fetch().

De fout herkennen en oplossen​

Access to fetch at 'https://api.voorbeeld.be/producten' from origin
'http://localhost:5173' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.

Drie dingen die je meteen weet als je dit ziet:

  1. Het probleem zit niet in je JavaScript, je URL of je JSON.
  2. Het request is uitgevoerd: in de Network tab staat het er, vaak met 200.
  3. De oplossing ligt bij de server, niet bij je frontend.
SituatieWat je doet
De API is van jouZet de juiste Access-Control-Allow-Origin in je serverconfiguratie of middleware
De API is van iemand andersRoep ze aan vanaf je eigen server; die heeft geen last van CORS
Enkel tijdens ontwikkelingGebruik de proxy van je dev server, zodat alles vanuit één origin lijkt te komen

Zo'n dev-proxy ziet er in Vite zo uit: verzoeken naar /api gaan naar de echte API, maar je browser ziet enkel je eigen origin.

// vite.config.js
export default {
server: {
proxy: { '/api': 'https://api.voorbeeld.be' },
},
};
Wat je nooit doet

Een browserextensie installeren die CORS uitschakelt, of --disable-web-security gebruiken. Het probleem verdwijnt dan enkel op jouw machine: je bezoekers krijgen de fout gewoon. Bovendien zet je daarmee de beveiliging uit voor élke site die je daarna bezoekt.

Dezelfde fetch(), twee keer naast elkaar. Let op waar het rechts misloopt: niet bij de server, die antwoordt gewoon, maar bij de browser, die de response niet aan je code doorgeeft.

Schema van de Same-Origin Policy: links gaat een verzoek naar dezelfde origin gewoon door, rechts antwoordt de server op een cross-origin verzoek maar houdt de browser de response tegen omdat de header Access-Control-Allow-Origin ontbreektDezelfde originpagina én API op http://localhost:5173Jouw codeBrowserServerfetch('/producten')GET /producten200 OK + JSONresponseAlles gaat door.Zelfde scheme, host en poort, dus de browser stelt geen vragen.Andere originpagina op localhost:5173, API op api.voorbeeld.beJouw codeBrowserServerfetch('https://api.voorbeeld.be')GET /productenOrigin: localhost:5173200 OK + JSONgeen Access-Control-Allow-Originblocked by CORS policyBLOKKADE ZIT HIERDe server heeft gewoon geantwoord.De browser houdt de response tegen: jouw code krijgt ze nooit te zien.

Test jezelf​

1. Je `fetch()` wordt geblokkeerd door CORS. Wat is er met het request gebeurd?

2. Welke response header stuurt dit gedrag?

3. Je pagina draait op `https://app.voorbeeld.be` en roept `https://api.voorbeeld.be` aan. Is dat cross-origin?

4. Waarom blijf je ingelogd terwijl HTTP stateless is?

Onthoud
  • HTTP is stateless: elk request draagt zelf het bewijs van wie je bent, via een cookie of een token.
  • De server zet een cookie met Set-Cookie; de browser stuurt het daarna automatisch terug met Cookie.
  • HttpOnly, Secure en SameSite bepalen hoe goed dat cookie beschermd is.
  • Een origin is scheme + host + poort; alle drie moeten gelijk zijn.
  • De Same-Origin Policy is een regel van de browser: het request vertrekt wél, maar je code krijgt de response niet.
  • CORS heft die regel op via Access-Control-Allow-Origin; de oplossing ligt dus altijd bij de server, of bij een proxy.