
Le message ressemble à peu près à ceci, et la moitié qui compte, c’est la fin :
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.
Lisez-le comme une phrase sur celui qui a refusé. Le serveur a répondu. Votre requête a quitté le navigateur, atteint l’autre machine, et elle est revenue avec une réponse. Le navigateur a ensuite refusé de remettre cette réponse à votre JavaScript, parce que la réponse ne disait pas que votre origine avait le droit de la lire.
Ce seul fait écarte l’essentiel de ce que l’on essaie d’abord. Rien n’est cassé sur le réseau. Le serveur n’est pas tombé. Aucun pare-feu ne fait obstacle. La réponse est là, dans le navigateur, et le navigateur ne la transmettra pas.
Pourquoi le navigateur fait cela
Sans cela, n’importe quelle page que vous visitez pourrait discrètement adresser des requêtes à votre banque, à votre webmail ou aux outils internes de votre entreprise avec les cookies déjà présents dans votre navigateur, et en lire les réponses.
La règle est donc la suivante : JavaScript peut envoyer une requête vers une autre origine, mais ne peut lire la réponse que si le serveur qui répond dit que cette origine en a le droit. La permission doit venir du serveur interrogé, parce qu’il est le seul à savoir qui devrait la lire.
Deux conséquences qui surprennent :
- Cela ne concerne que le JavaScript du navigateur. La même requête depuis curl, depuis Postman ou depuis votre propre backend fonctionne, parce qu’aucun d’eux n’est un navigateur qui protège la session de quelqu’un. Une requête qui passe dans Postman et échoue dans la page ne prouve pas que le serveur est cassé.
- La désactiver dans votre navigateur ne corrige rien. Un drapeau ou une extension qui coupe la vérification fait disparaître l’erreur sur une seule machine, tandis que chaque visiteur continue de l’avoir. C’est un moyen de confirmer le diagnostic, pas un correctif, et utiliser un navigateur ainsi au quotidien est une très mauvaise idée.
Lire quelle règle a échoué
La fin du message nomme la règle, et il y en a trois que vous rencontrerez.
No ‘Access-Control-Allow-Origin’ header is present. (Aucun en-tête ‘Access-Control-Allow-Origin’ n’est présent.) Le cas simple. Le serveur n’a rien envoyé au sujet des permissions, donc le navigateur suppose qu’il n’y en a aucune.
Response to preflight request doesn’t pass access control check. (La réponse à la requête
preflight ne passe pas le contrôle d’accès.) L’échec s’est produit avant même que votre requête soit
envoyée. Tout ce qui dépasse une requête simple, un PUT ou un DELETE, un en-tête personnalisé
comme Authorization, un type de contenu JSON, fait envoyer d’abord au navigateur une requête
OPTIONS qui demande si la vraie est autorisée. Si cet OPTIONS renvoie un 404, un 500, une
redirection, ou un 200 sans les bons en-têtes, la vraie requête n’a jamais lieu.
*The value of ‘Access-Control-Allow-Origin’ must not be the wildcard ‘’ when credentials mode is
‘include’.** (La valeur ne doit pas être le joker ‘*’ quand les identifiants sont envoyés.) Vous
envoyez des cookies, et * n’est pas assez précis pour cela. Le serveur doit nommer votre origine
exactement et ajouter Access-Control-Allow-Credentials: true.
Où va le correctif
Sur le serveur qui possède la ressource, dans tous les cas. Pas dans votre page, pas dans le navigateur, pas dans un proxy que vous placez devant votre propre front end.
Si c’est votre API, cela veut dire renvoyer Access-Control-Allow-Origin avec l’origine que vous
voulez autoriser et, pour les preflights, répondre à OPTIONS avec les méthodes et les en-têtes
autorisés et un statut 2xx. Si c’est l’API de quelqu’un d’autre et qu’elle n’autorise pas l’accès
depuis un navigateur, alors l’accès depuis un navigateur n’est pas proposé : appelez-la depuis votre
propre backend et laissez votre page dialoguer avec celui-ci.
Session Replay
Extension Chrome gratuite. Un clic sur la page qui se comporte mal capture la copie d'écran, la console et le journal réseau, et vous rend un lien à coller dans le ticket.
Sur localhost, là où la plupart des gens la rencontrent
http://localhost:3000 et http://localhost:8080 sont des origines différentes, donc un front end
sur un port qui appelle une API sur un autre fait une requête inter-origines et a droit à tout le
traitement.
La bonne réponse en développement est un proxy : votre serveur de développement transmet /api à
l’API, si bien que le navigateur ne voit qu’une seule origine et que la question ne se pose jamais.
Tout outil front end moderne le propose d’origine et cela tient en une ligne de configuration. La
mauvaise réponse est de lancer Chrome avec la sécurité web désactivée, parce que le code ne
fonctionne alors que pour ceux qui ont fait pareil.
Ce que votre code voit
Rien d’utile, et c’est justement ce qui rend le débogage déroutant.
Une réponse bloquée n’arrive pas sous la forme d’une erreur que vous pouvez inspecter. fetch() est
rejeté avec le générique
TypeError: Failed to fetch,
sans statut et sans corps, parce que laisser la page lire pourquoi elle a été bloquée divulguerait
précisément l’information que la règle existe pour protéger.
Le message CORS dans la console n’est donc pas une erreur que votre code a attrapée. C’est le navigateur qui vous dit, à vous le développeur, ce qu’il a fait. Votre code ne peut ni le voir, ni le journaliser, ni le remonter.
Cela vaut la peine de le savoir quand le signalement vient de quelqu’un d’autre. Un utilisateur qui
tombe sur un échec CORS voit une fonctionnalité qui ne fait silencieusement rien, votre suivi des
erreurs enregistre un Failed to fetch sans détail, et la phrase qui aurait tout expliqué a été
écrite dans une console que personne n’a conservée.
Le retrouver dans un rapport
Deux endroits, et il vous les faut tous les deux.
La console porte le message CORS avec l’origine, l’URL et la règle en défaut. Le panneau
réseau montre la requête elle-même, et, pour un échec de preflight, la requête OPTIONS posée
au-dessus de la vraie, avec son propre statut. Ouvrez ses en-têtes de réponse : ce qui manque là est
tout le diagnostic.
Si la requête n’apparaît pas du tout dans le panneau réseau, le navigateur a refusé avant l’envoi, ce qui désigne du contenu mixte ou une extension plutôt que CORS.
En bref
- Le serveur a répondu ; le navigateur a refusé de laisser votre code lire la réponse.
- Cela ne concerne que le JavaScript du navigateur, donc curl et Postman ne prouvent rien.
- La fin du message nomme la règle en défaut. Lisez cela, pas le début.
- Les échecs de preflight surviennent avant votre requête, sur un
OPTIONSque le navigateur a envoyé pour vous. - Le correctif appartient au serveur qui répond. Désactiver la vérification en local est un diagnostic, pas un correctif.
- Sur localhost, utilisez un proxy de développement pour qu’il n’y ait qu’une seule origine.