Die Meldung sieht ungefähr so aus, und die wichtige Hälfte ist ihr Ende:

Access to fetch at 'https://api.example.com/orders' from origin
'https://app.example.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.

Lesen Sie sie als Satz darüber, wer sich geweigert hat. Der Server hat geantwortet. Ihre Anfrage hat den Browser verlassen, die andere Maschine erreicht und ist mit einer Antwort zurückgekommen. Der Browser hat es dann abgelehnt, diese Antwort an Ihr JavaScript zu übergeben, weil die Antwort nicht sagte, dass Ihre Origin sie lesen darf.

Diese eine Tatsache schließt das meiste aus, was man zuerst versucht. Im Netzwerk ist nichts kaputt. Der Server ist nicht ausgefallen. Es steht keine Firewall im Weg. Die Antwort liegt im Browser, und der Browser gibt sie nicht weiter.

Warum der Browser das überhaupt tut

Ohne das könnte jede Seite, die Sie besuchen, mit den Cookies, die ohnehin schon in Ihrem Browser liegen, unbemerkt Anfragen an Ihre Bank, Ihr Webmail oder die internen Werkzeuge Ihrer Firma stellen und die Antworten lesen.

Die Regel lautet also: JavaScript darf eine Anfrage an eine fremde Origin schicken, darf die Antwort aber nur dann lesen, wenn der antwortende Server sagt, dass diese Origin das darf. Die Erlaubnis muss von dem Server kommen, der gefragt wird, denn er ist der Einzige, der weiß, wer das lesen soll.

Zwei Folgen, die überraschen:

  • Es gilt nur für JavaScript im Browser. Dieselbe Anfrage aus curl, aus Postman oder aus Ihrem eigenen Backend funktioniert, denn keines davon ist ein Browser, der jemandes Sitzung schützt. Eine Anfrage, die in Postman klappt und in der Seite scheitert, ist kein Beleg für einen kaputten Server.
  • Es im Browser abzuschalten behebt nichts. Ein Flag oder eine Erweiterung, die die Prüfung ausschaltet, lässt den Fehler auf einer Maschine verschwinden, während ihn jeder Besucher weiter bekommt. Das ist ein Weg, die Diagnose zu bestätigen, keine Lösung, und einen Browser im Alltag so zu betreiben ist wirklich eine schlechte Idee.

Ablesen, welche Regel gescheitert ist

Das Ende der Meldung benennt die Regel, und es gibt drei, die Ihnen begegnen werden.

No ‘Access-Control-Allow-Origin’ header is present. (Es ist kein ‘Access-Control-Allow-Origin’-Header vorhanden.) Der einfache Fall. Der Server hat nichts über Erlaubnisse geschickt, also nimmt der Browser an, dass es keine gibt.

Response to preflight request doesn’t pass access control check. (Die Antwort auf die Preflight-Anfrage besteht die Zugriffskontrolle nicht.) Der Fehlschlag ist passiert, bevor Ihre Anfrage überhaupt abgeschickt wurde. Alles, was über eine einfache Anfrage hinausgeht, ein PUT oder DELETE, ein eigener Header wie Authorization, ein JSON-Content-Type, lässt den Browser zuerst eine OPTIONS-Anfrage schicken, die fragt, ob die eigentliche erlaubt ist. Wenn dieses OPTIONS ein 404, ein 500, eine Weiterleitung oder ein 200 ohne die richtigen Header zurückgibt, kommt die eigentliche Anfrage nie zustande.

*The value of ‘Access-Control-Allow-Origin’ must not be the wildcard ‘’ when credentials mode is ‘include’.** (Der Wert darf nicht der Platzhalter ‘*’ sein, wenn Anmeldeinformationen mitgeschickt werden.) Sie schicken Cookies mit, und * ist dafür nicht genau genug. Der Server muss Ihre Origin exakt benennen und Access-Control-Allow-Credentials: true hinzufügen.

Wohin die Lösung gehört

Auf den Server, dem die Ressource gehört, in jedem Fall. Nicht in Ihre Seite, nicht in den Browser und nicht in einen Proxy, den Sie vor Ihr eigenes Frontend setzen.

Wenn es Ihre API ist, heißt das, Access-Control-Allow-Origin mit der Origin zurückzugeben, die Sie erlauben wollen, und für Preflights OPTIONS mit den erlaubten Methoden und Headern und einem 2xx-Status zu beantworten. Wenn es die API eines anderen ist und sie keinen Zugriff aus dem Browser erlaubt, dann steht der Zugriff aus dem Browser nicht zur Verfügung: Rufen Sie sie aus Ihrem eigenen Backend auf und lassen Sie Ihre Seite mit diesem sprechen.

Session Replay

Kostenlose Chrome-Erweiterung. Ein Klick auf der Seite, die sich falsch verhält, erfasst den Screenshot, die Konsole und das Netzwerkprotokoll und gibt Ihnen einen Link, den Sie ins Ticket einfügen können.

Erweiterung holen

Auf localhost, wo die meisten es kennenlernen

http://localhost:3000 und http://localhost:8080 sind verschiedene Origins, also ist ein Frontend auf dem einen Port, das eine API auf dem anderen aufruft, eine Cross-Origin-Anfrage und bekommt die volle Behandlung.

Die richtige Antwort in der Entwicklung ist ein Proxy: Ihr Dev-Server leitet /api an die API weiter, der Browser sieht also eine einzige Origin und die Frage stellt sich gar nicht. Jedes moderne Frontend-Werkzeug hat das eingebaut, und es kostet eine Zeile Konfiguration. Die falsche Antwort ist, Chrome mit abgeschalteter Web-Sicherheit zu starten, denn dann funktioniert der Code nur für Leute, die das auch getan haben.

Was Ihr Code sieht

Nichts Brauchbares, und genau das macht die Fehlersuche hier so verwirrend.

Eine blockierte Antwort kommt nicht als Fehler an, den Sie untersuchen könnten. fetch() wird mit dem generischen TypeError: Failed to fetch abgelehnt, ohne Status und ohne Body, denn die Seite lesen zu lassen, warum sie blockiert wurde, würde genau die Information preisgeben, zu deren Schutz die Regel da ist.

Die CORS-Meldung in der Konsole ist also kein Fehler, den Ihr Code abgefangen hat. Es ist der Browser, der Ihnen, dem Entwickler, sagt, was er getan hat. Ihr Code kann sie nicht sehen, nicht protokollieren und nicht melden.

Das ist gut zu wissen, wenn die Meldung von jemand anderem kommt. Ein Nutzer, der in einen CORS-Fehlschlag läuft, sieht eine Funktion, die stillschweigend nichts tut, Ihr Error-Tracking verzeichnet ein Failed to fetch ohne Details, und der Satz, der es erklärt hätte, wurde in eine Konsole geschrieben, die niemand aufbewahrt hat.

Wie Sie es in einem Bericht finden

Zwei Stellen, und Sie wollen beide.

Die Konsole trägt die CORS-Meldung mit der Origin, der URL und der gescheiterten Regel. Das Netzwerkpanel zeigt die Anfrage selbst, und bei einem Preflight-Fehlschlag die OPTIONS-Anfrage, die über der eigentlichen steht, mit ihrem eigenen Status. Öffnen Sie deren Response-Header: was dort fehlt, ist die ganze Diagnose.

Wenn die Anfrage im Netzwerkpanel gar nicht auftaucht, hat der Browser sich schon vor dem Senden geweigert, und das heißt gemischte Inhalte oder eine Erweiterung, nicht CORS.

Die Kurzfassung

  • Der Server hat geantwortet; der Browser hat Ihrem Code verwehrt, die Antwort zu lesen.
  • Es gilt nur für JavaScript im Browser, also beweisen curl und Postman nichts.
  • Das Ende der Meldung benennt die gescheiterte Regel. Lesen Sie das, nicht den Anfang.
  • Preflight-Fehlschläge passieren vor Ihrer Anfrage, auf einem OPTIONS, das der Browser für Sie geschickt hat.
  • Die Lösung gehört auf den antwortenden Server. Die Prüfung lokal abzuschalten ist eine Diagnose, keine Lösung.
  • Nutzen Sie auf localhost einen Dev-Proxy, damit es nur eine Origin gibt.