CORS-Fehler beheben
„Blocked by CORS policy“ ist der Browser, der deine Nutzer schützt, und kein Fehler in deinem Code. Die Lösung liegt immer auf dem Server, der die Anfrage empfängt. Dieser Leitfaden erklärt, was der Browser prüft, und zeigt funktionierende Konfigurationen für die häufigsten Fälle.
Was CORS eigentlich ist
Browser blockieren JavaScript auf https://app.example.com, wenn es Antworten von einem anderen Origin (Schema + Host + Port) lesen will, etwa https://api.example.com, sofern der Server das nicht erlaubt. Der Server signalisiert das mit den Antwort-Headern Access-Control-Allow-*. Anfragen von curl, Postman oder deinem Backend sind davon nicht betroffen. Deshalb funktioniert etwas in Postman, scheitert aber im Browser.
Die Fehlermeldung lesen
- „No ‘Access-Control-Allow-Origin’ header is present“: Der Server hat gar keine CORS-Header gesendet oder eine Fehlerseite (404, 500) ohne diese Header ausgeliefert.
- „Response to preflight request doesn’t pass access control check“: Die
OPTIONS-Anfrage ist fehlgeschlagen. Sie wurde nicht verarbeitet, hat einen Fehler zurückgegeben oder es fehlten Header. - „The value of the ‘Access-Control-Allow-Origin’ header must not be the wildcard ‘*’ when credentials mode is ‘include’“: Du sendest Cookies, also muss der Server den exakten Origin zurückgeben.
Schau dir die fehlgeschlagene Anfrage in den DevTools unter Network an oder prüfe die tatsächlichen Antwort-Header mit dem HTTP Header Checker.
Einfache Anfragen und Preflight
Eine Anfrage ist „einfach“, wenn sie eine GET-, HEAD- oder POST-Anfrage mit nur grundlegenden Headern und einem Formular- oder Klartext-Body ist. Alles andere, etwa Content-Type: application/json, ein Authorization-Header, PUT oder DELETE, lässt den Browser zuerst eine OPTIONS-Preflight-Anfrage senden. Dein Server muss sie mit Status 204 oder 200 und diesen Headern beantworten:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600
Funktionierende Konfigurationen
Express mit dem Paket cors:
const cors = require('cors');
app.use(cors({
origin: ['https://app.example.com'],
credentials: true, // only if you send cookies
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
Nginx:
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 600;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
proxy_pass http://backend;
}
Das Flag always ist wichtig: Ohne es lässt Nginx den Header bei Fehlerantworten weg. Der Browser meldet dann einen CORS-Fehler statt des echten 500er-Fehlers. Header für andere Server (Vercel, Netlify, Cloudflare Workers, Apache) erzeugst du mit dem CORS Header Generator.
Cookies und Credentials
Damit Cookies domänenübergreifend mitgesendet werden, braucht die Anfrage credentials: 'include' (fetch), und der Server muss mit Access-Control-Allow-Credentials: true sowie dem exakten Origin antworten, niemals mit *. Das Cookie selbst braucht SameSite=None; Secure. Wenn du den Origin aus einer Liste zurückgibst, sende zusätzlich Vary: Origin, damit Caches nicht die Header der einen Seite an eine andere ausliefern.
Was du nicht tun solltest
- Gib keinen
Origin-Header ungeprüft zurück, wenn Credentials aktiviert sind. Sonst kann jede Website die Daten deiner Nutzer lesen. - Repariere CORS nicht mit einer Browser-Erweiterung oder einem öffentlichen Proxy. Das versteckt das Problem nur auf deinem Rechner.
- Wenn die API dir gehört und auf derselben Seite liegt, entfernt ein Reverse Proxy (gleicher Origin) CORS komplett.
Die Anfrage des Browsers kannst du mit dem API Tester nachstellen oder mit cURL to Code in Code umwandeln.