Hoe deze controle werkt
Scanaris vraagt je homepage opnieuw op met een Origin-header van een verzonnen domein, zoals de browser zou doen als een andere website je probeerde aan te roepen, en kijkt wat je server antwoordt. Geeft Access-Control-Allow-Origin diezelfde origin terug en staat bovendien Access-Control-Allow-Credentials op true, dan opent het een bevinding met hoge ernst: je server spiegelt elke origin met credentials. Antwoordt hij met de wildcard * samen met credentials, dan opent het er een met lage ernst: browsers weigeren die combinatie, maar hij verraadt een blind gemaakte configuratie. Alleen de gescande URL wordt getest; leeft je API op een ander pad of domein, test die dan met het commando hieronder.
Wat een aanvaller hiermee doet
Hij zet op een willekeurig domein een pagina op met een fetch naar je API met credentials: 'include'. Wanneer een van je gebruikers, met een open sessie, die pagina bezoekt, voegt de browser zijn cookies toe, antwoordt je server met de data van de gebruiker en, omdat het CORS-beleid het toelaat, leest de pagina van de aanvaller ze uit. Profiel, bestellingen, berichten, alles wat de API teruggeeft. De gebruiker ziet niets: het gebeurt op de achtergrond.
Waarom het gebeurt
CORS bestaat zodat de browser standaard cross-domain-leesbewerkingen blokkeert. Als een aanroep faalt tijdens de ontwikkeling (de app op de ene poort, de API op de andere), is de snelle oplossing uit tutorials en AI-tools "alle origins toestaan", en om cookies te laten werken komt er "met credentials" bij. In productie betekent dat dat de bescherming is uitgezet. Meer details in verkeerd geconfigureerde CORS: de stille fout die je gebruikers blootstelt.
Hoe je het oplost
- Expliciete lijst van origins. Alleen de domeinen die je API echt vanuit de browser aanroepen. In Express:
cors({ origin: ['https://app.jouwdomein.com'], credentials: true }). In Next.js vergelijk je in de Route Handler deOriginmet je lijst voordat je hem teruggeeft. In Nuxt metrouteRulesen de optiecorsof een servermiddleware. - Spiegel nooit de origin. De waarde van
Originin het antwoord kopiëren "omdat dan alles werkt" is precies de fout die de controle meet. - Heb je geen cookies tussen domeinen nodig, verwijder dan
Access-Control-Allow-Credentials. Zonder credentials is een*op een publieke alleen-lezen-API aanvaardbaar. - Voeg Vary: Origin toe wanneer het antwoord van de origin afhangt, zodat geen enkele cache aan het ene domein het antwoord van het andere serveert.
- Cookies met SameSite=Lax of Strict als tweede barrière: zelfs als CORS faalt, stuurt de browser ze niet vanaf een andere site.
Controleer of het is opgelost
curl -sI -H "Origin: https://evil.example" https://jouwdomein.com/api/me | grep -i "access-control"
De juiste uitkomst is dat Access-Control-Allow-Origin voor die origin niet verschijnt, of verschijnt met jouw domein en niet met evil.example. Test ook met je eigen domein in Origin om te bevestigen dat je app nog werkt.
Veelgestelde vragen
Mijn API is publiek en zonder login. Is dat een probleem?
Nee, als er geen cookies of autorisatieheaders zijn. Access-Control-Allow-Origin: * zonder credentials is de juiste configuratie voor een open API.
Ik heb meerdere subdomeinen die de API aanroepen.
Som ze allemaal op of valideer de Origin tegen een strikt patroon van je rootdomein. Valideer nooit met "bevat jouwdomein.com": jouwdomein.com.evil.example voldoet daaraan.
En localhost om te ontwikkelen?
Voeg het alleen toe in de ontwikkelomgeving, via een omgevingsvariabele. Een http://localhost:3000 in de productielijst is een origin die elk programma op de computer van de gebruiker kan gebruiken.