CORS für kleine Webanwendungen - Festlegen, welche Browser-Ursprünge eine Antwort lesen dürfen
Die Browserkonsole meldet, eine Anfrage sei „blocked by CORS“. Dieselbe API liefert im Terminal vielleicht trotzdem 200 OK, und durch das Hinzufügen von Access-Control-Allow-Origin: * scheint der Fehler zu verschwinden. Was genau hat dieser Header erlaubt?
Die hilfreiche Antwort ist enger als „domainübergreifender Zugriff“. CORS ist ein Browserprotokoll, das entscheidet, ob Code eines Ursprungs auf eine Antwort eines anderen Ursprungs zugreifen darf. Es ist keine allgemeine Firewall für den Endpunkt und entscheidet nicht, ob ein Benutzer authentifiziert ist oder einen bestimmten Datensatz bearbeiten darf. Wenn diese Grenzen sichtbar bleiben, werden sowohl Fehlersuche als auch Sicherheitsprüfung übersichtlicher.
Beim Ursprung beginnen, nicht nur bei der Domain
Bei gewöhnlichen HTTP- und HTTPS-URLs wird ein Ursprung durch die Kombination aus Schema, Host und Port bestimmt. RFC 6454 beschreibt dieses Ursprungsmodell als Schutzgrenze, die User Agents verwenden. Pfade erzeugen keine getrennten Ursprünge, eine Änderung von Schema, Host oder Port hingegen schon.
https://app.example/accountundhttps://app.example/settingshaben denselben Ursprung.http://app.exampleundhttps://app.examplehaben unterschiedliche Ursprünge, weil sich die Schemata unterscheiden.https://app.exampleundhttps://api.examplehaben unterschiedliche Ursprünge, weil sich die Hosts unterscheiden.https://app.exampleundhttps://app.example:8443haben unterschiedliche Ursprünge, weil sich die Ports unterscheiden.
Deshalb löst „aber beide Dienste gehören demselben Unternehmen“ keinen CORS-Fehler. Die organisatorische Eigentümerschaft ist kein Bestandteil des Ursprungsvergleichs im Browser. Der Browser sieht zwei Schutzbereiche; der Server muss jede Freigabe zwischen ihnen bewusst beschreiben.
CORS steuert den Browserzugriff auf die Antwort
Die Same-Origin Policy verhindert im Allgemeinen, dass ein Skript Informationen eines anderen Ursprungs liest. Das Web hat jedoch nie jede Art des ursprungsübergreifenden Sendens verboten. Links navigieren zwischen Websites, Bilder werden von anderen Websites geladen und HTML-Formulare können an einen anderen Ursprung gesendet werden. RFC 6454 unterscheidet die strengere Grenze für das Lesen von der historisch großzügigeren Möglichkeit des Sendens.
CORS ist eine kontrollierte Lockerung dieser Lesegrenze. Angenommen, von https://app.example geladenes JavaScript ruft öffentliche Daten unter https://api.example/status ab. Der Browser fügt den Request-Header Origin hinzu:
GET /status HTTP/1.1
Host: api.example
Origin: https://app.example
Wenn die API dem Browsercode dieses Ursprungs Zugriff auf die Antwort geben möchte, kann sie so antworten:
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: https://app.example
Vary: Origin
{"status":"available"}
Der Browser vergleicht die Antwortregel mit dem anfragenden Ursprung. Besteht sie die CORS-Prüfung, kann das aufrufende Skript die Antwort verwenden. Fehlt der erforderliche Header oder stimmt er nicht überein, gibt der Browser die Antwort nicht an dieses Skript weiter.
Der letzte Satz hat eine wichtige Einschränkung: Eine fehlgeschlagene CORS-Prüfung beweist nicht, dass der Server die Anfrage nie empfangen oder verarbeitet hat. Manche ursprungsübergreifenden Anfragen können ohne vorherige Prüfung gesendet werden. Ein CORS-bewusstes Anwendungsdesign muss serverseitige Aktionen daher auch dann schützen, wenn keine Antwort an JavaScript freigegeben wird.
Der Preflight fragt nach der geplanten Anfrage
Bei manchen ursprungsübergreifenden Anfragen sendet der Browser vor der eigentlichen Anfrage selbstständig eine OPTIONS-Anfrage. Das ist der CORS-Preflight. Er tritt häufig auf, wenn Code eine Methode wie PUT oder DELETE, einen nicht auf der Safelist stehenden Request-Header oder einen Content-Type außerhalb der CORS-Safelist verwenden möchte. Die genauen Bedingungen stehen in den Safelist-Regeln des Fetch Standard, der als Living Standard fortlaufend gepflegt wird. Eine kurze Merkregel ist daher nicht die vollständige Spezifikation.
Für eine JSON-Aktualisierung mit einem Authorization-Header kann ein Preflight so aussehen:
OPTIONS /profile HTTP/1.1
Host: api.example
Origin: https://app.example
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type
Eine eng begrenzte Antwort könnte lauten:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Authorization, Content-Type
Vary: Origin
Erfüllt die Antwort die CORS-Prüfungen des Browsers, darf dieser das eigentliche PUT senden. Dieser Austausch ist eine Protokollverhandlung und kein Nachweis, dass der Benutzer das Profil aktualisieren darf. Der eigentliche Handler muss den Aufrufer weiterhin authentifizieren, den Zugriff auf das gewählte Profil autorisieren, den Body validieren und alle vom Bedrohungsmodell verlangten Kontrollen der Anfrageintegrität anwenden.
Ein Preflight geht auch nicht garantiert jeder ursprungsübergreifenden Anfrage voraus. Ein auf der Safelist stehendes POST, darunter Anfragen in der Form klassischer HTML-Formularübermittlungen, kann ohne Preflight gesendet werden. Wer einen destruktiven Endpunkt auf der Annahme „der Browser führt dafür einen Preflight aus“ aufbaut, platziert die eigentliche Sicherheitsentscheidung in der falschen Schicht.
Credentials machen die Freigabeentscheidung sensibler
Ursprungsübergreifendes fetch() sendet standardmäßig keine Credentials. Der Clientcode muss sie beispielsweise mit credentials: "include" anfordern, und der Server muss die Freigabe einer Antwort mit Credentials ausdrücklich erlauben. Nach den Credential-Regeln des Fetch Standard darf der Server eine Antwort mit Credentials nicht mit Access-Control-Allow-Origin: * kombinieren. Er benötigt einen konkreten erlaubten Ursprung und dieses Response-Feld:
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Credentials: true
Vary: Origin
Das zwingt einen Browser nicht, ein Cookie zu senden. Cookie-Domain, Pfad, Secure, SameSite und die Richtlinie des Browsers für Drittanbieter-Cookies bleiben unabhängige Bedingungen. Der MDN-Leitfaden zu Anfragen mit Credentials weist ausdrücklich darauf hin, dass die normale Richtlinie für Drittanbieter-Cookies weiterhin gilt. Ein CORS-Header kann sie nicht außer Kraft setzen.
Für die Freigabe mit Credentials ist eine kleinere Allowlist angemessen, weil die lesbare Antwort benutzerspezifische Informationen enthalten kann. Ist ein Endpunkt tatsächlich öffentlich und verwendet keine Credentials, kann * einen öffentlichen, im Browser lesbaren Zugriff korrekt ausdrücken. Der Platzhalter ist nicht grundsätzlich ein Fehler; er ist ungeeignet, wenn die beabsichtigte Zielgruppe kleiner als alle Ursprünge ist.
CORS keine Aufgaben geben, die es nicht erfüllen kann
Mehrere Sicherheitsfragen liegen nahe bei CORS, bleiben aber unabhängig:
- Authentifizierung: Wer stellt die Anfrage? Eine Session, ein Token, ein Client-Zertifikat oder ein anderer Mechanismus beantwortet diese Frage.
- Autorisierung: Darf diese Identität diesen Vorgang an diesem Objekt ausführen? Die Anwendung muss dies für jede geschützte Aktion entscheiden.
- CSRF-Schutz: Hat ein Browser mit automatisch mitgesendeten Credentials eine unerwünschte zustandsändernde Anfrage gesendet? Token, geeignete Cookie-Einstellungen und Prüfungen des Anfragekontexts behandeln diese Bedrohung.
- CORS: Darf Browsercode dieses Ursprungs auf diese ursprungsübergreifende Antwort zugreifen?
PortSwigger warnt davor, CORS als CSRF-Schutz zu verstehen. Ein ursprungsübergreifendes Formular kann bestimmte Anfragen senden, ohne die Antwort lesen zu dürfen. Umgekehrt darf ein korrekt zugelassener Frontend-Ursprung weiterhin keine Datensätze erhalten, für die der angemeldete Benutzer keine Berechtigung besitzt.
CORS beschränkt einen direkten HTTP-Client außerdem nicht so, wie es Browser-JavaScript beschränkt. Ein Kommandozeilen-Client, Backend-Dienst oder automatisiertes Skript kann einen über das Internet erreichbaren Endpunkt aufrufen, ohne die CORS-Antwortprüfung eines Browsers durchzusetzen. Authentifizierung, Autorisierung, Rate Limits und Netzwerkkontrollen müssen eigenständig funktionieren.
Die Allowlist als Richtlinie statt als String-Dekoration entwerfen
Wenn mehrere Frontends legitim sind, kann der Server den eingehenden Origin mit einer exakten Allowlist vergleichen und den Wert nur bei einer Übereinstimmung zurückgeben. Wer jeden gelieferten Origin ungeprüft nach Access-Control-Allow-Origin kopiert, verwandelt scheinbare Validierung in eine Freigabe für jeden Ursprung.
Auch lockere Suffix-Prüfungen sind leicht misszuverstehen. Eine Prüfung, die nur auf example.com am Ende achtet, kann einen unbeabsichtigten Host wie notexample.com akzeptieren. Schema und Port zählen ebenfalls. Exakt serialisierte Ursprünge sind leichter zu prüfen:
https://app.example
https://admin.example
https://preview.example:8443
Der serialisierte Wert null kann in bestimmten Browserkontexten für opake oder datenschutzsensible Ursprünge stehen. Er sollte nicht beiläufig als Abkürzung für lokale Dateien oder Sandbox-Dokumente zugelassen werden. Braucht ein Produkt ihn tatsächlich, verdient diese Vertrauensentscheidung eine eigene Bedrohungsanalyse, statt unter „Entwicklung erlauben“ versteckt zu werden.
Wählt der Server den erlaubten Ursprung dynamisch, teilt Vary: Origin Caches mit, dass die Auswahl der Antwort vom Origin der Anfrage abhängt. Ohne diese Unterscheidung könnte ein gemeinsam genutzter Cache Antwortmetadaten, die für einen Ursprung gewählt wurden, bei einem anderen wiederverwenden. Dieses Feld sorgt für korrekte Cache-Auswahl; es ersetzt die Allowlist nicht.
Die Richtlinie von beiden Seiten testen
Ein Terminal ist hilfreich, um zu prüfen, wie der Server auf einen vorgeschlagenen Ursprung und Preflight reagiert:
curl -i https://api.example/status \
-H 'Origin: https://app.example'
curl -i -X OPTIONS https://api.example/profile \
-H 'Origin: https://app.example' \
-H 'Access-Control-Request-Method: PUT' \
-H 'Access-Control-Request-Headers: authorization, content-type'
Diese Befehle beweisen nicht, dass ein Browser die endgültige Antwort zugänglich macht. curl zeigt, was der Server sendet; es implementiert nicht die Same-Origin Policy des Browsers. Der vollständige Test muss in einem echten Browser von jedem vorgesehenen Frontend-Ursprung erfolgen.
Eine kleine Testmatrix sollte Folgendes enthalten:
- Einen erlaubten Ursprung sowie die erwartete Methode und die erwarteten Header.
- Einen nicht aufgeführten Ursprung, der keinen Zugriff gewährenden Allow-Origin-Wert erhalten darf.
- Einen ähnlich aussehenden Host sowie ein falsches Schema oder einen falschen Port.
- Einen fehlenden
Origin, entsprechend dem normalen Nicht-CORS-Verhalten des Endpunkts. - Anfragen mit und ohne Credentials als getrennte Fälle.
- Eine abgelehnte Methode und einen abgelehnten benutzerdefinierten Header im Preflight.
- Erfolg, Validierungsfehler, Authentifizierungsfehler und Serverfehler, weil inkonsistente Header nur manche Antworten unlesbar machen können.
- Antworten über jedes beteiligte CDN oder jeden Reverse Proxy, wobei geprüft wird, ob
Vary: Originund der gewählte Allow-Origin-Wert korrekt erhalten bleiben.
Die Browserkonsole kann erklären, warum die CORS-Prüfung fehlgeschlagen ist. Im Network-Panel lässt sich erkennen, ob ein Preflight stattfand und welche Response-Header ankamen. Serverlogs beantworten eine andere Frage: was der Endpunkt tatsächlich empfangen und verarbeitet hat. Alle drei Perspektiven verhindern, dass ein Fehler der Browserrichtlinie als API-Ausfall diagnostiziert oder eine serverseitige Aktion übersehen wird, nur weil JavaScript ihre Antwort nicht lesen konnte.
Die Grenze ausdrücklich festlegen
Eine sorgfältige CORS-Richtlinie beginnt mit einer Bestandsaufnahme: Welche Browserursprünge benötigen welche Antworten, sind Credentials beteiligt und welche Methoden und Request-Header sind tatsächlich erforderlich? Öffentliche Ressourcen können öffentlichen Zugriff ausweisen. Private Integrationen sollten exakte Ursprünge und die kleinstmögliche praktikable Oberfläche verwenden. Endpunkte, die keinen ursprungsübergreifenden Browserzugriff benötigen, brauchen keine großzügigen CORS-Header.
Die zentrale Frage lautet nicht „Wie beseitige ich den CORS-Fehler?“, sondern „Welcher Browsercode soll diese Antwort lesen dürfen, und welche unabhängigen Kontrollen schützen den Vorgang selbst?“ Werden beide Fragen getrennt beantwortet, wird CORS zu einem präzisen Freigabeprotokoll statt zu einem rätselhaften Sicherheitsschalter.
References
- WHATWG Fetch Standard: CORS protocol - Living Standard, last updated October 6, 2026 at time of access.
- RFC 6454: The Web Origin Concept - IETF / RFC Editor, December 2011.
- MDN Web Docs: Cross-Origin Resource Sharing (CORS) - last modified September 4, 2026 at time of access.
- PortSwigger Web Security Academy: CORS and the Access-Control-Allow-Origin response header.
