Comment corriger les erreurs CORS
« Blocked by CORS policy » est une protection du navigateur pour vos utilisateurs, et non un bug de votre code. La correction se fait toujours côté serveur qui reçoit la requête. Ce guide explique ce que vérifie le navigateur et donne des configurations fonctionnelles pour les cas courants.
Ce qu’est réellement CORS
Les navigateurs empêchent le JavaScript sur https://app.example.com de lire les réponses d’une autre origine (protocole + hôte + port), comme https://api.example.com, sauf si ce serveur l’autorise. Le serveur l’indique avec des en-têtes de réponse Access-Control-Allow-*. Les requêtes émises depuis curl, Postman ou votre backend ne sont pas concernées : c’est pourquoi « ça marche dans Postman » alors que cela échoue dans le navigateur.
Lire le message d’erreur
- « No ‘Access-Control-Allow-Origin’ header is present » : le serveur n’a envoyé aucun en-tête CORS, ou une page d’erreur (404, 500) a été renvoyée sans eux.
- « Response to preflight request doesn’t pass access control check » : la requête
OPTIONSa échoué : elle n’a pas été gérée, a renvoyé une erreur ou n’avait pas les bons en-têtes. - « The value of the ‘Access-Control-Allow-Origin’ header must not be the wildcard ‘*’ when credentials mode is ‘include’ » : vous envoyez des cookies, le serveur doit donc renvoyer l’origine exacte.
Examinez la requête en échec dans DevTools → Réseau, ou vérifiez les vrais en-têtes de réponse avec le HTTP Header Checker.
Requêtes simples et preflight
Une requête est « simple » si c’est un GET, HEAD ou POST avec seulement des en-têtes de base et un corps de formulaire ou de texte brut. Tout le reste — Content-Type: application/json, un en-tête Authorization, PUT ou DELETE — amène le navigateur à envoyer d’abord une requête OPTIONS de preflight. Votre serveur doit y répondre avec le statut 204 ou 200 et ces en-têtes :
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
Configurations fonctionnelles
Express avec le paquet cors :
const cors = require('cors');
app.use(cors({
origin: ['https://app.example.com'],
credentials: true, // uniquement si vous envoyez des 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;
}
Le drapeau always est important : sans lui, Nginx supprime l’en-tête sur les réponses d’erreur, et le navigateur signale alors une erreur CORS à la place du véritable 500. Pour générer les en-têtes d’autres serveurs (Vercel, Netlify, Cloudflare Workers, Apache), utilisez le CORS Header Generator.
Cookies et identifiants
Pour envoyer des cookies entre origines, la requête doit contenir credentials: 'include' (fetch) et le serveur doit répondre avec Access-Control-Allow-Credentials: true ainsi que l’origine exacte, jamais *. Le cookie lui-même doit avoir SameSite=None; Secure. Lorsque vous renvoyez l’origine depuis une liste, ajoutez aussi Vary: Origin pour que les caches ne servent pas les en-têtes d’un site à un autre.
Ce qu’il ne faut pas faire
- Ne renvoyez pas n’importe quel en-tête
Originavec les identifiants activés : cela permet à tous les sites de lire les données de vos utilisateurs. - Ne « corrigez » pas CORS avec une extension de navigateur ou un proxy public : cela masque seulement le problème sur votre machine.
- Si l’API vous appartient et se trouve sur le même site, un reverse proxy (même origine) supprime complètement le besoin de CORS.
Reproduisez la requête du navigateur avec l’API Tester, ou transformez-la en code avec cURL to Code.