Ihr Frontend läuft auf http://localhost:5173, Ihre API auf http://localhost:4000, und der fetch-Aufruf, der in Postman anstandslos funktionierte, schlägt im Browser mit einer Wand aus rotem Text fehl: Access to fetch at 'http://localhost:4000/api/users' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Ihr Server hat die Anfrage womöglich verarbeitet und eine 200 zurückgegeben. Der Browser hat sich einfach geweigert, die Antwort an Ihr JavaScript weiterzugeben.
Diese Weigerung ist die Same-Origin-Policy. Mit CORS teilt ein Server dem Browser mit, diese Regel für bestimmte Aufrufer zu lockern, und jede Lösung weiter unten läuft auf einen Response-Header hinaus, den der Browser nicht bekommen hat.
Was die Same-Origin-Policy wirklich blockiert
Eine Origin ist die Kombination aus Schema, Host und Port. http://localhost:5173 und http://localhost:4000 sind unterschiedliche Origins, weil sich der Port unterscheidet, auch wenn Host und Schema übereinstimmen. https://api.example.com und http://api.example.com sind ebenfalls unterschiedliche Origins, weil sich das Schema unterscheidet: Eine läuft über TLS, die andere nicht.
Die Same-Origin-Policy ist eine Browser-Regel, keine Server-Regel. Ein Skript, das auf einer Origin läuft, kann die Antwort einer Anfrage an eine andere Origin nicht lesen, sofern diese Origin es nicht ausdrücklich erlaubt. Bei einer einfachen GET geht die Anfrage trotzdem raus und der Server verarbeitet sie trotzdem: Sie erscheint in den Server-Logs und im Network-Tab mit einem echten Statuscode. Was der Browser zurückhält, ist der Response-Body. Deshalb sieht ein CORS-Fehler wie ein clientseitiges Problem aus, während der Server nichts Falsches meldet.
Der Sinn dahinter: Eine Seite auf evil.example soll Ihre angemeldete Browser-Sitzung nicht nutzen können, um API-Antworten Ihrer Bank zu lesen. Ohne diese Regel könnte jede Website, die Sie besuchen, authentifizierte Anfragen an jede andere Website senden und die Antworten lesen.
Was CORS erlaubt, und was es nicht schützt
Cross-Origin Resource Sharing ist eine Opt-in-Ausnahme von dieser Regel. Der Server fügt Response-Header hinzu, die festlegen, welche Origins seine Antworten lesen dürfen, und der Browser hält sich daran, statt zu blockieren.
Der Server lehnt nichts ab. CORS-Header sind Anweisungen an den Browser, keine Zugriffskontrolle. Eine curl-Anfrage, ein Backend-zu-Backend-Aufruf oder eine native mobile App erreichen denselben Endpunkt ganz ohne CORS-Prüfung. Wenn Ihre API Schutz braucht, ist das Sache von Authentifizierung und Autorisierung.
Wann der Browser eine CORS-Preflight-Anfrage sendet
Der Browser teilt jede Cross-Origin-Anfrage in eine von zwei Kategorien ein, und nur eine davon fragt vorher um Erlaubnis.
Eine Anfrage ist simple, wenn all das zutrifft:
- Die Methode ist
GET,HEADoderPOST. - Die einzigen gesetzten Header sind die, die der Browser automatisch setzt, plus eine kurze erlaubte Liste (
Accept,Accept-Language,Content-Language,Content-Type,Range). - Falls
Content-Typegesetzt ist, lautet der Wertapplication/x-www-form-urlencoded,multipart/form-dataodertext/plain.
Eine simple Anfrage geht direkt raus, und der Browser prüft die Response-Header, um zu entscheiden, ob Ihr Skript den Body bekommt. Alles andere ist preflighted: eine PUT oder DELETE, ein JSON-Body (Content-Type: application/json), oder ein benutzerdefinierter Header wie Authorization oder X-API-Key. Bevor die eigentliche Anfrage gesendet wird, schickt der Browser eine OPTIONS-Anfrage und fragt den Server, was er erlaubt:
| |
Der Server antwortet mit dem, was er erlaubt. Nur wenn diese Antwort die tatsächliche Anfrage abdeckt, sendet der Browser sie:
| |
Access-Control-Max-Age gibt an, wie viele Sekunden der Browser diese Antwort zwischenspeichern und den Preflight bei der nächsten passenden Anfrage überspringen darf. Eine JSON-API, die ein Bearer-Token sendet, löst bei fast jedem Aufruf einen Preflight aus, dieser Header spart also jedes Mal einen Round Trip. Browser deckeln das: Chromium cacht nicht länger als 7200 Sekunden, egal welchen Wert Sie senden.
CORS-Response-Header und was sie bewirken
| Header | Was er bewirkt |
|---|---|
Access-Control-Allow-Origin | Die Origin, die die Antwort lesen darf: eine wörtliche Origin, oder * für jede Origin. * wird bei Anfragen mit Credentials abgelehnt. |
Access-Control-Allow-Methods | Die für diese Ressource erlaubten Methoden, zurückgegeben in der Preflight-Antwort. |
Access-Control-Allow-Headers | Die erlaubten Request-Header, zurückgegeben in der Preflight-Antwort. Authorization muss namentlich aufgeführt sein; * deckt es nicht ab. |
Access-Control-Allow-Credentials | true lässt den Browser die Antwort für eine Anfrage mit Credentials (Cookies, HTTP-Authentifizierung) freigeben. Erfordert eine wörtliche Origin, nicht *. |
Access-Control-Expose-Headers | Response-Header über die Standard-Safelist hinaus, die JavaScript lesen darf. |
Die Credentials-Regel ist die, an der die meisten scheitern. „Credentials“ meint hier Cookies und vom Browser verwaltete HTTP-Authentifizierung, gesendet, wenn Sie fetch(url, { credentials: 'include' }) aufrufen. Bei einer solchen Anfrage muss der Server Access-Control-Allow-Credentials: true senden und die exakte Origin in Access-Control-Allow-Origin nennen. Eine Antwort mit Credentials und Access-Control-Allow-Origin: * wird abgelehnt, selbst wenn alle anderen Header korrekt sind.
Ein Bearer-Token, das Sie selbst in einem Authorization-Header setzen, ist in diesem Sinne kein Credential. Es ist ein benutzerdefinierter Header: Er löst einen Preflight aus und muss in Access-Control-Allow-Headers auftauchen, braucht aber kein Allow-Credentials.
Wie Sie CORS-Fehler in Express beheben
Das Paket cors setzt diese Header für Sie.
| |
| |
In Produktion haben Sie oft mehr als ein legitimes Frontend. Übergeben Sie ein Array oder eine Funktion statt eines einzelnen Strings:
| |
Mit app.use eingebunden, beantwortet cors() den OPTIONS-Preflight für jede Route. Wenn Sie es nur auf bestimmten Routen mit einer anderen Methode als GET einbinden, fügen Sie app.options("/api/users", cors()) hinzu, damit auch der Preflight für diesen Pfad eine Antwort bekommt.
Wie Sie CORS in ASP.NET Core aktivieren
Registrieren Sie eine benannte Policy in Program.cs und rufen Sie UseCors zwischen UseRouting und UseAuthorization auf. Die Reihenfolge zählt: Ein Preflight enthält keine Authentifizierung, läuft die Autorisierung also zuerst, lehnt sie die OPTIONS-Anfrage ab, bevor CORS antworten kann.
| |
WithOrigins akzeptiert nur wörtliche Origins, aus demselben Grund wie der rohe Header. Kombinieren Sie AllowCredentials() mit AllowAnyOrigin(), wirft ASP.NET Core eine InvalidOperationException, statt eine Antwort zu senden, die der Browser ohnehin ablehnen würde. Für eine Steuerung pro Endpunkt setzen Sie [EnableCors("_allowFrontend")] auf einen Controller oder eine Action und rufen app.UseCors() ohne Policy-Namen auf: Die Middleware muss trotzdem in der Pipeline stehen, damit das Attribut wirkt.
Wie Sie CORS in der Entwicklung mit einem Vite-Proxy umgehen
Auf Ihrem Rechner ist die einfachste Lösung, gar keine Cross-Origin-Anfrage mehr zu stellen. Vite, webpack-dev-server und die meisten anderen Dev-Server können einen Pfad zu Ihrem Backend proxen, sodass der Browser nur mit einer einzigen Origin spricht:
| |
Ein fetch an /api/users geht jetzt an den Vite-Dev-Server auf http://localhost:5173, der ihn an http://localhost:4000 weiterleitet und die Antwort durchreicht. Der Browser sieht nur eine Origin, also gibt es für CORS nichts zu blockieren.
Das ist eine Entwicklungshilfe. In Produktion entspricht dem ein Reverse Proxy, der Frontend und API von einer einzigen öffentlichen Origin ausliefert, wodurch CORS-Header auf dieser API vollständig überflüssig werden.
CORS-Workarounds, die Sie nicht verwenden sollten
Jeder dieser Wege lässt den roten Text bei Ihnen verschwinden und lässt das eigentliche Problem, einen Header, den Ihre API nicht sendet, unverändert bestehen:
Access-Control-Allow-Origin: *auf einem Endpunkt, der Cookies verwendet. Der Browser lehnt die Antwort mit Credentials ab, das behebt den Fehler also nicht einmal. Entfernen Sie die Cookies, damit die Wildcard funktioniert, und Sie haben einen Endpunkt, der jeder Website im Internet ohne jede Origin-Prüfung antwortet.- Eine „CORS unblock“-Browser-Erweiterung. Sie patcht Antworten innerhalb Ihres eigenen Browsers. Der Fehler verschwindet bei Ihnen und bleibt für jeden echten Nutzer bestehen, auch für einen Kollegen, der Staging von einem anderen Rechner testet.
- Chrome mit
--disable-web-securitystarten. Das deaktiviert die Same-Origin-Policy für alle Tabs dieser Browser-Instanz, nicht nur für Ihre App, solange sie geöffnet bleibt. Ein vergessener Tab auf einer bösartigen Website hebelt genau den Schutz aus, für den die Policy existiert.
Welche CORS-Lösung Sie verwenden sollten
Wenn der Fehler nur lokal auftritt, gegen ein Backend auf einem anderen Port, verwenden Sie den Dev-Server-Proxy und überspringen Sie die CORS-Konfiguration komplett.
Wenn echte Nutzer Ihre API cross-origin von einer separaten Frontend-Domain oder einer Drittanbieter-Website aus aufrufen, setzen Sie Access-Control-Allow-Origin auf die exakten Origins, die Sie bedienen wollen. Fügen Sie Access-Control-Allow-Credentials nur hinzu, wenn der Browser Cookies sendet. Öffnen Sie dann den Network-Tab der DevTools und prüfen Sie die OPTIONS-Anfrage, bevor Sie annehmen, dass es funktioniert hat: Ein 2xx mit Allow-Origin, Allow-Methods und Allow-Headers, die Ihre eigentliche Anfrage abdecken, bedeutet, dass die darauffolgende Anfrage durchgeht.