JavaScript Fetch: Ursprungsübergreifende Anfragen (CORS)
Ursprungsübergreifende Anfragen mit der JavaScript Fetch API: CORS-Funktionsweise, mode- und credentials-Optionen, Preflight-Anfragen und Fehlerbehandlung.
Ursprungsübergreifende Anfragen ermöglichen es einer Webseite, Daten von einem Server mit einem anderen Ursprung als die Seite selbst zu laden. Sie bilden das Rückgrat fast jeder modernen App: der Aufruf einer öffentlichen API, die Kommunikation mit dem eigenen Backend auf einer anderen Subdomain oder die Einbindung eines Drittanbieter-Dienstes. Diese Seite erklärt, wie die CORS-Regeln des Browsers funktionieren, wie man mit der Fetch API ursprungsübergreifende Anfragen stellt und wie man Zugangsdaten, Preflight-Anfragen und Fehler korrekt behandelt.
Was gilt als "ursprungsübergreifend"
Ein Ursprung ist die Kombination aus Protokoll + Host + Port. Zwei URLs teilen denselben Ursprung nur dann, wenn alle drei übereinstimmen. Unterscheidet sich auch nur eines davon, ist eine Anfrage zwischen ihnen ursprungsübergreifend.
Anfrage von https://app.example.com an… | Gleicher Ursprung? | Warum |
|---|---|---|
https://app.example.com/api/users | Ja | identisches Protokoll, Host, Port |
http://app.example.com/api | Nein | unterschiedliches Protokoll (http vs https) |
https://api.example.com/users | Nein | unterschiedlicher Host (Subdomain zählt) |
https://app.example.com:8443/api | Nein | unterschiedlicher Port |
Anfragen desselben Ursprungs sind uneingeschränkt. Ursprungsübergreifende Anfragen werden durch CORS geregelt.
Was CORS ist (und was nicht)
Cross-Origin Resource Sharing (CORS) ist ein Browser-Sicherheitsmechanismus, der entscheidet, ob JavaScript auf einem Ursprung eine Antwort von einem anderen Ursprung lesen darf. Die Entscheidung trifft der Server, der durch das Senden bestimmter HTTP-Antwort-Header seine Zustimmung gibt. Der Browser setzt diese dann durch.
Zwei Dinge werden leicht verwechselt:
- CORS ist kein Problem, das sich rein im Frontend-Code lösen lässt. Wenn der Server nicht die richtigen Header sendet, kann keine
fetch-Option das Lesen ermöglichen. - Die Anfrage erreicht den Server oft trotzdem und wird dort ausgeführt; CORS blockiert nur, dass dein Skript die Antwort liest. Deshalb ist CORS kein Ersatz für Authentifizierung.
Eine ursprungsübergreifende Anfrage mit Fetch stellen
Die Fetch API ist der moderne, Promise-basierte Weg, HTTP-Anfragen zu stellen. Ein einfacher fetch-Aufruf an einen anderen Ursprung ist bereits ursprungsübergreifend — der Browser behandelt CORS automatisch.
Das funktioniert, weil jsonplaceholder.typicode.com den Header Access-Control-Allow-Origin: * zurückgibt. Würde er das nicht tun, würde der Browser das Lesen blockieren und fetch würde ablehnen. Weitere Informationen zum vollständigen Anfrage-/Antwortmodell findest du im Kapitel Fetch API.
Die Server-Header, die es ermöglichen
Wenn du eine ursprungsübergreifende Anfrage stellst, muss der Server CORS-Header einschließen, um sie zu erlauben. Die am häufigsten verwendeten:
Access-Control-Allow-Origin— welche(r) Ursprung(e) die Antwort lesen darf (ein exakter Ursprung oder*für alle).Access-Control-Allow-Methods— welche HTTP-Methoden erlaubt sind (verwendet in Preflight-Antworten).Access-Control-Allow-Headers— welche Anfrage-Header der Client senden darf (verwendet in Preflight-Antworten).Access-Control-Allow-Credentials— ob Cookies/Auth gesendet werden dürfen (musstruesein, um Zugangsdaten zu erlauben).
Diese werden auf dem Server konfiguriert, nicht in fetch. Das Frontend steuert nur die Anfrage.
Die Option mode
Die mode-Option von Fetch legt fest, wie mit ursprungsübergreifenden Anfragen umgegangen werden soll:
'cors'— der Standard. Die Anfrage ist nur erlaubt, wenn der Server passende CORS-Header zurückgibt; andernfalls wird das Lesen blockiert.'same-origin'— schlägt sofort fehl bei jeder ursprungsübergreifenden URL.'no-cors'— sendet die Anfrage, gibt aber eine opake Antwort zurück: Status, Header oder Body sind nicht lesbar. Nützlich nur für Fire-and-Forget-Fälle wie das Cachen eines Bildes in einem Service Worker.
Da 'cors' bereits der Standard ist, muss es selten gesetzt werden, aber eine explizite Angabe dokumentiert die Absicht:
Verwende auf dem Server in der Produktion nicht Access-Control-Allow-Origin: *. Erlaube nur die spezifischen Ursprünge, denen du vertraust.
Zugangsdaten senden
Standardmäßig senden ursprungsübergreifende fetch-Anfragen keine Cookies oder HTTP-Auth-Header. Um diese einzuschließen, setze die Option credentials auf 'include'. Der Server muss ebenfalls mit Access-Control-Allow-Credentials: true antworten und deinen genauen Ursprung in Access-Control-Allow-Origin angeben — * wird bei der Verwendung von Zugangsdaten abgelehnt. Weitere Informationen, wie Cookies über Seiten hinweg funktionieren, findest du unter Cookies und document.cookie.
async function fetchWithCredentials(url) {
try {
const response = await fetch(url, {
mode: 'cors',
credentials: 'include'
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
fetchWithCredentials('https://api.crossorigin.com/secure-data');Wenn eine CORS-Richtlinie einen ursprungsübergreifenden Lesevorgang blockiert, gibt der Browser deinem Skript die Antwort nicht weiter. Stattdessen lehnt fetch mit einem TypeError ab ("Failed to fetch"), sodass der Fehler in deinem catch-Block landet — nicht in der if (!response.ok)-Prüfung.
Preflight-Anfragen
Bei Anfragen, die der Browser als potenziell unsicher einstuft, sendet er zunächst automatisch eine OPTIONS-Anfrage — einen Preflight — um den Server zu fragen, ob die eigentliche Anfrage erlaubt ist. Dein Code löst diesen OPTIONS-Aufruf nie aus; der Browser erledigt das für dich.
Eine Anfrage löst einen Preflight aus, wenn sie keine "einfache Anfrage" ist, das heißt, wenn sie:
- eine andere Methode als
GET,HEADoderPOSTverwendet; - benutzerdefinierte Anfrage-Header enthält (zum Beispiel
AuthorizationoderX-Api-Key); - einen anderen
Content-Typealsapplication/x-www-form-urlencoded,multipart/form-dataodertext/plainverwendet (das Senden von JSON mitapplication/jsonist der häufigste Auslöser).
Ein typischer JSON-POST kostet daher zwei Hin- und Rückwege — den Preflight, dann die eigentliche Anfrage:
async function createPost(url, payload) {
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // triggers a preflight
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
}
}
createPost('https://api.example.com/posts', { title: 'Hello' });Damit der Preflight erfolgreich ist, muss der Server die OPTIONS-Anfrage mit Access-Control-Allow-Methods und Access-Control-Allow-Headers beantworten, die das abdecken, was die eigentliche Anfrage verwenden wird. Schlägt er fehl, löst der Browser einen Netzwerkfehler aus und die eigentliche Anfrage wird nie gesendet.
Fehlertypen unterscheiden
CORS-Fehler sind notorisch verwirrend, weil der Browser Details aus Sicherheitsgründen verbirgt. Verwende diese Checkliste:
fetchlehnt ab ("Failed to fetch") — meist ein CORS-Block, ein Netzwerkfehler oder ein fehlgeschlagener Preflight. Prüfe die Browser-Konsole auf die spezifische CORS-Meldung; sie wird JavaScript nicht preisgegeben.response.okistfalse— die Anfrage war erfolgreich und CORS hat bestanden, aber der Server hat einen 4xx/5xx-Status zurückgegeben. Das ist ein Anwendungsfehler, kein CORS-Problem.- Cookies werden nicht gesendet — du hast
credentials: 'include'vergessen, oder der Server gibtAccess-Control-Allow-Credentials: truenicht mit einem spezifischen Ursprung zurück.
Umschließe Aufrufe mit try/catch und prüfe response.ok separat, wie in den obigen Beispielen gezeigt.
Best Practices
Access-Control-Allow-Origineinschränken. Liste die exakten Ursprünge auf, denen du vertraust, anstatt*zu verwenden, besonders wenn Zugangsdaten im Spiel sind (*wird mit Zugangsdaten abgelehnt).- Immer HTTPS verwenden. Es schützt Daten während der Übertragung und ist für viele Secure-Context-APIs erforderlich.
- Preflights reduzieren. Vermeide unnötige benutzerdefinierte Header und lass den Server
Access-Control-Max-Agesenden, damit der Browser Preflight-Ergebnisse cacht. - Fehler elegant behandeln. Trenne Transport-/CORS-Fehler (
catch) von HTTP-Statusfehlern (!response.ok) und zeige dem Benutzer nützliche Meldungen an. - CORS nicht als Sicherheitsmaßnahme verwenden. Es steuert, was Browser Skripten lesen lassen; es authentifiziert den Aufrufer nicht. Validiere und autorisiere jede Anfrage serverseitig.
Verwandte Kapitel
- Fetch API — die Grundlage für jede Anfrage auf dieser Seite.
- Eine Fetch-Anfrage abbrechen — langsame oder unerwünschte Anfragen abbrechen.
- Mit JSON arbeiten — Anfrage- und Antwort-Bodies parsen und stringifizieren.
- Cookies: document.cookie — wie Cookies mit ursprungsübergreifenden Zugangsdaten interagieren.
Zusammenfassung
Eine Anfrage ist ursprungsübergreifend, wenn sich ihr Protokoll, Host oder Port von dem der Seite unterscheidet. CORS lässt den Server entscheiden, welche Ursprünge die Antwort lesen dürfen, und der Browser setzt diese Entscheidung durch. Mit Fetch ist der 'cors'-Modus der Standard; verwende die Option credentials, um Cookies zu senden, rechne bei nicht-einfachen Aufrufen mit einer Preflight-OPTIONS-Anfrage und behandle CORS-Fehler in catch, während du response.ok auf HTTP-Ebene prüfst.