Déboguer une erreur CORS : guide pratique côté serveur
Vous déployez un nouveau frontend, ouvrez la console et découvrez une erreur rouge indiquant que la requête a été « bloquée par la politique CORS ». Votre API fonctionne dans Apidog ou avec curl, mais le navigateur refuse de transmettre la réponse à votre JavaScript. Frustrant ? Oui. Mystérieux ? Non, dès que vous savez où chercher.
Essayez Apidog dès aujourd’hui
Le point essentiel souvent oublié : CORS est appliqué par le navigateur, mais provoqué par le serveur. Le navigateur bloque la réponse lorsque l’API n’envoie pas les bons en-têtes Access-Control-Allow-*. La correction se trouve donc presque toujours dans la configuration du serveur, pas dans le code frontend.
Ce guide présente :
- le fonctionnement de CORS ;
- la requête de pré-vérification (preflight) ;
- les six erreurs les plus courantes ;
- des configurations fonctionnelles pour Express, Spring Boot et Nginx ;
- une méthode de débogage avec Apidog, sans navigateur.
Qu’est-ce qu’une erreur CORS ?
CORS signifie Cross-Origin Resource Sharing — partage de ressources entre origines multiples.
Par défaut, les navigateurs appliquent la politique de même origine. Un script exécuté sur https://app.example.com ne peut pas lire la réponse de https://api.example.com, car le schéma, l’hôte ou le port diffère. CORS permet au serveur d’assouplir explicitement cette règle.
Pour approfondir, consultez la documentation CORS de MDN et la spécification Fetch.
Trois principes expliquent la plupart des problèmes :
-
Le navigateur applique CORS. Les appels serveur à serveur,
curlet les clients API de bureau l’ignorent. - Le serveur le configure. Le navigateur prend sa décision à partir des en-têtes de réponse.
- La requête atteint souvent quand même l’API. Pour une requête simple, le serveur traite la demande puis le navigateur masque la réponse au JavaScript.
CORS n’est donc pas un pare-feu autour de l’API. Il protège les utilisateurs contre les pages malveillantes qui tenteraient de lire des données inter-origines avec leurs cookies.
Lorsque vous voyez une erreur CORS, ne cherchez pas de contournement frontend : identifiez l’en-tête manquant ou incorrect et corrigez le serveur.
Comprendre la requête de pré-vérification
Avant certaines requêtes inter-origines, le navigateur envoie une requête OPTIONS, appelée preflight.
Elle est déclenchée notamment lorsque la requête :
- utilise une méthode autre que
GET,HEADouPOST; - envoie des en-têtes personnalisés comme
Authorization; - utilise un type de contenu comme
application/json.
Exemple :
OPTIONS /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
Le navigateur demande en substance : « Une page sur app.example.com peut-elle envoyer une requête POST avec ces en-têtes ? »
Réponse correcte :
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Vary: Origin
Si un élément manque, le navigateur annule la requête réelle avant son lancement. Le point de terminaison ne s’exécute pas et les journaux ne montrent généralement qu’une requête OPTIONS.
Access-Control-Max-Age indique au navigateur combien de temps mettre en cache ce verdict, ici 86400 secondes.
Pour déboguer, commencez toujours par cette question :
Le preflight a-t-il échoué, ou la requête réelle a-t-elle échoué ?
Les six erreurs CORS les plus courantes
1. Access-Control-Allow-Origin est absent
Le serveur renvoie une réponse sans en-tête CORS :
Access-Control-Allow-Origin: https://app.example.com
Utilisez une origine précise ou * pour une API publique sans identifiants.
Attention aux réponses d’erreur : un middleware peut ajouter les en-têtes aux réponses 200, mais pas aux 401, 403 ou 500. Dans ce cas, le navigateur affiche une erreur CORS au lieu de la véritable erreur serveur.
Ajoutez les en-têtes CORS à toutes les réponses, y compris les erreurs 403 Forbidden.
2. * est incompatible avec les identifiants
Ce message apparaît lorsque le frontend utilise :
credentials: 'include'
mais que le serveur renvoie :
Access-Control-Allow-Origin: *
La spécification Fetch interdit cette combinaison. Renvoyez l’origine exacte et activez les identifiants :
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Validez toujours l’en-tête Origin contre une liste blanche. Ne reflétez jamais une origine arbitraire lorsque les identifiants sont activés.
3. La réponse au preflight échoue
Le serveur ne traite peut-être pas OPTIONS :
- la route ne définit que
POST; - elle renvoie
404ou405; - un middleware d’authentification renvoie
401avant le middleware CORS.
Un preflight ne contient pas le jeton d’authentification habituel. Gérez donc OPTIONS avant l’authentification :
app.options('/v1/orders', (req, res) => {
res.set({
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Authorization, Content-Type'
});
res.sendStatus(204);
});
Dans la plupart des frameworks, monter le middleware CORS en premier suffit.
4. L’origine autorisée ne correspond pas
Le serveur renvoie bien Access-Control-Allow-Origin, mais avec une valeur différente de l’en-tête Origin.
Causes fréquentes :
-
https://app.example.comest configuré alors que le frontend utilisehttp://localhost:5173; -
httpethttpsne correspondent pas ; - une barre oblique finale a été ajoutée.
Une origine valide ne se termine pas par /.
Comparez exactement l’origine reçue à une liste blanche :
const allowed = ['https://app.example.com', 'http://localhost:5173'];
if (allowed.includes(req.headers.origin)) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Vary', 'Origin');
}
Vary: Origin évite qu’un cache ou un CDN réutilise l’en-tête d’une origine pour une autre.
5. La méthode ou l’en-tête n’est pas autorisé
Exemples de messages :
-
authorizationn’est pas autorisé parAccess-Control-Allow-Headers; - la méthode
PUTn’est pas autorisée parAccess-Control-Allow-Methods.
Le preflight a atteint le serveur, mais sa réponse ne couvre pas les besoins de la requête réelle.
Déclarez toutes les méthodes et tous les en-têtes utilisés :
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-Id
Les noms d’en-têtes sont insensibles à la casse. Les méthodes doivent être écrites en majuscules.
6. Une redirection est utilisée pendant le preflight
Le preflight reçoit un 301 ou 302. Les navigateurs refusent généralement de suivre les redirections pendant cette phase.
Les causes typiques sont :
- une URL HTTP redirigée vers HTTPS ;
- une barre oblique finale ajoutée par le routeur ;
- une passerelle qui redirige
/v1/ordersvers/v1/orders/.
Utilisez directement l’URL finale, commencez en HTTPS et respectez la convention de votre routeur. Vérifiez que la requête OPTIONS renvoie un 2xx, et non un 3xx.
Configurations serveur fonctionnelles
Express
Utilisez le middleware cors officiel :
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Authorization', 'Content-Type'],
credentials: true,
maxAge: 86400
}));
Montez-le avant le middleware d’authentification afin que les requêtes de pré-vérification ne soient pas rejetées pour absence de jeton.
Avec Flask, le même modèle est disponible via l’extension Flask-CORS.
Spring Boot
Configuration globale avec WebMvcConfigurer :
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/v1/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(86400);
}
}
Avec Spring Security, ajoutez également .cors(Customizer.withDefaults()) dans la chaîne de filtres. Sinon, Spring Security peut bloquer les preflight avant que la configuration MVC ne soit appliquée.
Consultez la documentation CORS de Spring pour les autres options.
Nginx
Si Nginx termine les requêtes devant l’application, traitez les preflight à la périphérie :
location /v1/ {
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, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Vary "Origin" always;
proxy_pass http://backend;
}
Le paramètre always est indispensable : sans lui, Nginx supprime les en-têtes add_header sur les réponses 4xx et 5xx.
Ne configurez CORS que dans une seule couche. Si Nginx et l’application ajoutent les mêmes en-têtes, le navigateur peut recevoir une valeur invalide comme :
Access-Control-Allow-Origin: *, *
Déboguer CORS avec Apidog
L’erreur de console indique que le navigateur a bloqué quelque chose, mais pas ce que le serveur a réellement envoyé. Retirez le navigateur de la boucle avec Apidog, un client API de bureau qui n’est pas soumis aux vérifications CORS.
Si la requête réussit dans Apidog, la logique de l’API fonctionne probablement et le problème concerne ses en-têtes CORS. Si elle échoue également, vous avez plutôt un problème API classique. Les techniques générales de test d’API s’appliquent alors.
Procédure en quatre étapes
-
Rejouez la requête réelle. Copiez la requête échouée depuis l’onglet Réseau du navigateur et recréez-la dans Apidog avec la même méthode, les mêmes en-têtes et le même corps. Un
500confirme que CORS n’est pas le problème principal. -
Testez le preflight. Créez une requête
OPTIONSavec :
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
-
Inspectez les en-têtes. Vérifiez
Access-Control-Allow-Origin,Access-Control-Allow-MethodsetAccess-Control-Allow-Headers. Comparez-les aux besoins du frontend. -
Validez la correction. Renvoyez la même requête
OPTIONSaprès avoir modifié le serveur et vérifiez les nouvelles valeurs.
Ce workflow explique aussi pourquoi un test CORS dans Postman peut réussir alors que le navigateur échoue : les clients de bureau ignorent CORS.
Vous pouvez télécharger Apidog gratuitement et conserver la requête OPTIONS à côté de vos tests d’API.
Checklist CORS en 30 secondes
Avant de signaler un bug, vérifiez :
- La réponse contient-elle
Access-Control-Allow-Origin? - Sa valeur correspond-elle exactement à l’origine de la page : schéma, hôte et port, sans barre oblique finale ?
- Utilisez-vous des cookies ou une authentification ? Dans ce cas, utilisez une origine précise et
Access-Control-Allow-Credentials: true, jamais*. -
OPTIONSrenvoie-t-il un2xxavec les méthodes et en-têtes nécessaires ? - L’URL du preflight est-elle redirigée ?
- Les réponses
401,403et500contiennent-elles les mêmes en-têtes CORS que les réponses réussies ?
Dans la majorité des cas, l’une de ces vérifications révèle la cause. Confirmez-la avec une requête OPTIONS manuelle, corrigez le serveur et reprenez le développement.
FAQ
Pourquoi l’erreur CORS apparaît-elle uniquement dans le navigateur ?
Seuls les navigateurs appliquent CORS et la politique de même origine. curl, les services backend et les clients de bureau ne vérifient pas Access-Control-Allow-Origin.
Si une requête réussit partout sauf dans le navigateur, l’API est probablement saine, mais ses en-têtes CORS sont absents ou mal configurés.
CORS s’applique-t-il à Postman ou Apidog ?
Non. Postman et Apidog sont des applications de bureau, pas des pages exécutées dans le bac à sable d’un navigateur. Leurs requêtes contournent donc CORS et affichent les en-têtes bruts du serveur.
Une requête réussie dans un client de bureau ne garantit pas que le navigateur l’acceptera, mais elle permet d’isoler rapidement la couche défaillante.
Une erreur CORS est-elle une fonctionnalité de sécurité ou un bug ?
C’est une fonctionnalité de sécurité du navigateur. Le navigateur refuse d’exposer une réponse inter-origines à un script tant que le serveur n’y a pas consenti explicitement.
Désactiver CORS avec un drapeau ou une extension masque le problème uniquement sur votre machine. Corrigez les en-têtes du serveur.
Puis-je utiliser Access-Control-Allow-Origin: * partout ?
Uniquement pour les API publiques en lecture seule, sans cookies ni authentification.
Pour une API authentifiée, utilisez une liste blanche d’origines, renvoyez l’origine correspondante et ajoutez Vary: Origin afin que les caches partagés séparent correctement les réponses.
Top comments (0)