Menu

CORS Express : corriger l'erreur CORS, avec ou sans cors

Une erreur CORS signifie que le navigateur a empêché une page de lire la réponse de votre API, parce que la réponse n'autorisait pas l'origine de la page. Elle se corrige côté serveur en envoyant Access-Control-Allow-Origin et en répondant aux requêtes preflight OPTIONS, à la main ou avec le package cors.

Cette page contient des éditeurs exécutables. Modifiez, exécutez et voyez la sortie instantanément.

Une erreur CORS ("No 'Access-Control-Allow-Origin' header is present") signifie qu'une page web sur une origine, par exemple http://localhost:5173, a appelé votre API sur une autre origine, et que le navigateur a refusé de laisser la page lire la réponse, parce que la réponse n'indiquait pas que cette origine était autorisée. La correction se fait côté serveur : envoyez Access-Control-Allow-Origin avec l'origine de la page, et répondez à la requête preflight OPTIONS du navigateur avec les méthodes et les en-têtes que vous acceptez. Dans Express, c'est quelques lignes de middleware, ou app.use(cors()) avec le package cors.

La réponse est la liste des produits, et son onglet Headers affiche access-control-allow-origin: https://app.example.com : une page sur cette origine a le droit de la lire. Envoyez maintenant OPTIONS /api/products/1 avec les en-têtes Origin: https://app.example.com et Access-Control-Request-Method: PUT. C'est le preflight qu'un navigateur envoie avant un PUT, et la réponse est 204 avec les méthodes et en-têtes autorisés, sans corps.

Ce que vérifie le navigateur

Les pages de même origine n'ont besoin de rien. Pour une requête cross-origin, le navigateur lit ces en-têtes de réponse :

En-têteSignification
Access-Control-Allow-OriginLa seule origine autorisée à lire la réponse, ou * pour toutes
Access-Control-Allow-MethodsLes méthodes autorisées dans la vraie requête (preflight seulement)
Access-Control-Allow-HeadersLes en-têtes que la page peut envoyer, comme Authorization (preflight seulement)
Access-Control-Allow-Credentialstrue si la page peut envoyer des cookies et lire la réponse
Access-Control-Max-AgeLe nombre de secondes pendant lesquelles le navigateur peut garder la réponse du preflight en cache
Vary: OriginIndique aux caches que la réponse dépend de l'en-tête Origin de la requête

Un simple GET ou un POST de type formulaire part directement au serveur, et le navigateur vérifie Access-Control-Allow-Origin dans la réponse. Le serveur exécute quand même la requête ; CORS décide seulement si la page voit le résultat. Une requête avec un corps JSON, un en-tête Authorization ou une méthode comme PUT ou DELETE est précédée d'une requête preflight OPTIONS, et la vraie requête n'est envoyée que si la réponse au preflight l'autorise. C'est pour cela qu'un middleware CORS doit répondre à OPTIONS avant toute route ou vérification d'authentification.

Autoriser plusieurs origines, avec credentials

Access-Control-Allow-Origin contient une seule origine ou *, jamais une liste. Pour en autoriser plusieurs, comparez l'en-tête Origin de la requête avec une liste autorisée et renvoyez-le quand il correspond :

La première requête n'a pas d'en-tête Origin, comme un appel depuis curl ou une page de même origine, donc elle reçoit {"origin":null} et aucun en-tête CORS. Envoyez GET /api/me avec l'en-tête Origin: http://localhost:5173 et l'onglet Headers montre cette origine renvoyée, plus access-control-allow-credentials: true. Remplacez l'en-tête par Origin: https://evil.example et les deux en-têtes CORS disparaissent : le serveur répond toujours, mais un navigateur empêcherait la page de lire la réponse. Access-Control-Allow-Credentials: true permet à la page d'envoyer des cookies (avec fetch(url, { credentials: 'include' })), et les navigateurs le refusent en même temps que *, donc les credentials demandent toujours l'origine exacte.

Le package cors

Le package cors fait le même travail avec des options. Installez-le avec npm install cors :

const cors = require('cors');

// Any origin, no cookies: fine for a public read-only API.
app.use(cors());

// Only your frontend, with cookies.
app.use(cors({
    origin: ['https://app.example.com', 'http://localhost:5173'],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

// One route only.
app.get('/api/public', cors(), handler);

Appelé sans options, cors() envoie Access-Control-Allow-Origin: * et répond à tous les preflights. N'utilisez * que pour des données publiques qui ne demandent pas de connexion.

Erreurs fréquentes

Ajouter CORS après les routes ou après un middleware d'authentification. La requête preflight OPTIONS ne porte pas d'en-tête Authorization, donc une vérification d'authentification placée avant lui répond 401 et le navigateur signale une erreur CORS. Enregistrez le middleware CORS en premier.

Mettre * avec les credentials. Les navigateurs refusent la réponse. Renvoyez plutôt l'origine précise.

Essayer de corriger CORS dans le frontend. Les options de requête comme mode: 'no-cors' ne lèvent pas la restriction ; elles vous donnent une réponse opaque que vous ne pouvez pas lire. Seuls les en-têtes du serveur règlent le problème.

Pour le fonctionnement de app.use() et l'ordre des middlewares, voir les middlewares Express.

Questions fréquentes

Pourquoi mon API marche dans Postman ou curl mais pas dans le navigateur ?

Seuls les navigateurs appliquent CORS. Postman, curl et les autres serveurs ignorent les en-têtes CORS, donc ils lisent la réponse dans tous les cas. Le navigateur refuse de donner la réponse aux scripts d'une page d'une autre origine, sauf si le serveur autorise cette origine.

CORS est-il une protection pour mon serveur ?

Il protège les utilisateurs, pas votre serveur. Il empêche une page malveillante de lire les réponses de votre API avec les cookies de l'utilisateur. N'importe qui peut toujours appeler votre API depuis un script ou un serveur, donc l'authentification et les autorisations restent votre travail.

Qu'est-ce qui compte comme une origine différente ?

Une origine, c'est le schéma, l'hôte et le port ensemble. http://localhost:5173 et http://localhost:3000 sont des origines différentes parce que les ports diffèrent, et https://example.com et https://api.example.com diffèrent par l'hôte.

Peut-on éviter CORS en développement ?

Oui, en rendant la requête same-origin : configurez le serveur de dev du frontend (le server.proxy de Vite, par exemple) pour qu'il transmette /api au serveur Express. Le navigateur ne parle alors qu'à une seule origine et aucun en-tête CORS n'est nécessaire.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

Commencer