Comment corriger les erreurs CORS : Déboguer Access-Control-Allow-Origin

Vous rencontrez une erreur CORS ? Découvrez ce qui la déclenche, le fonctionnement du preflight, les 6 échecs Access-Control-Allow-Origin les plus fréquents, et la solution exacte pour chacun d'eux.

Ashley Innocent

Ashley Innocent

31 August 2026

Comment corriger les erreurs CORS : Déboguer Access-Control-Allow-Origin

Apidog pour les entreprises

Déploiement sur site

SSO & RBAC

Conforme SOC 2

Découvrir Apidog Enterprise

Vous déployez un nouveau frontend, ouvrez la console, et là : une erreur CORS rouge vous indiquant que la requête a été « bloquée par la politique CORS ». Votre API fonctionne parfaitement dans Apidog ou curl, pourtant le navigateur refuse de transmettre la réponse à votre JavaScript. Frustrant ? Oui. Mystérieux ? Non, une fois que vous savez d'où vient l'erreur.

Voici le fait essentiel que la plupart des tutoriels omettent : une erreur CORS est appliquée par le navigateur mais causée par le serveur. Le navigateur bloque la réponse parce que votre serveur n'a pas envoyé les bons en-têtes Access-Control-Allow-Origin. La solution se trouve donc presque toujours dans la configuration du serveur, et non dans votre code frontend.

Ce guide explique ce que fait CORS, comment fonctionne la requête de pré-vérification (preflight request), les six messages d'erreur CORS les plus courants avec la solution exacte pour chacun, et des configurations fonctionnelles pour Express, Spring Boot et Nginx. Vous verrez également comment déboguer en dehors du navigateur, ce qui est le moyen le plus rapide de distinguer une « mauvaise configuration du serveur » d'un « blocage par le navigateur ».

Qu'est-ce qu'une erreur CORS (et ce qu'elle n'est pas)

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 code JavaScript exécuté sur https://app.example.com ne peut pas lire les réponses de https://api.example.com, car le schéma, l'hôte ou le port diffère. CORS est le mécanisme que les serveurs utilisent pour assouplir intentionnellement cette règle. Les détails complets se trouvent dans la documentation CORS de MDN, et l'algorithme sous-jacent est défini dans la spécification Fetch.

Trois points clarifient la plupart des confusions :

Donc, lorsque vous voyez une erreur CORS, ne cherchez pas de solution de contournement côté frontend. Lisez le message d'erreur, puis corrigez l'en-tête manquant ou incorrect sur le serveur.

Anatomie de la requête de pré-vérification (preflight request)

Avant certaines requêtes inter-origines, le navigateur envoie un éclaireur : une requête OPTIONS appelée "preflight". Elle se déclenche lorsque votre requête utilise des méthodes autres que GET, HEAD ou POST, envoie des en-têtes personnalisés comme Authorization, ou utilise un Content-Type tel que application/json.

La requête preflight ressemble à ceci :

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 : « Une page sur app.example.com veut faire un POST ici avec ces en-têtes. Est-ce autorisé ? » Une réponse correcte du serveur :

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 une partie manque, le navigateur annule la requête réelle avant qu'elle ne soit lancée. Votre point de terminaison API ne s'exécute jamais, vos journaux ne montrent rien d'autre qu'une requête OPTIONS, et la console affiche une erreur CORS. Access-Control-Max-Age indique au navigateur de mettre en cache ce verdict (86400 secondes ici), de sorte que les requêtes répétées ignorent la pré-vérification.

Gardez cette danse en deux étapes à l'esprit. La moitié du débogage CORS se résume à une question : la pré-vérification a-t-elle échoué, ou la requête réelle a-t-elle échoué ?

Les 6 erreurs CORS les plus courantes et comment les corriger

Les navigateurs écrivent des messages d'erreur CORS étonnamment précis. Faites correspondre le vôtre à la liste ci-dessous.

1. L'en-tête ‘Access-Control-Allow-Origin’ est absent

Le classique. Votre serveur a envoyé une réponse sans aucun en-tête CORS. Le navigateur n'avait rien à évaluer, il a donc bloqué l'accès.

Solution : Configurez le serveur pour qu'il envoie Access-Control-Allow-Origin avec soit l'origine spécifique de la requête, soit * pour les API publiques sans identifiants :

Access-Control-Allow-Origin: https://app.example.com

Un piège : les réponses d'erreur omettent souvent les en-têtes CORS même lorsque les réponses de succès les incluent. Si votre API renvoie une 500 et que le middleware ne décore que les 200, la console affiche une erreur CORS au lieu de la véritable erreur serveur. Assurez-vous que les en-têtes CORS sont attachés à chaque réponse, y compris les pages 403 Forbidden et 500.

2. Le caractère générique ‘*’ ne peut pas être utilisé avec des identifiants

Le message indique : « La valeur de l'en-tête ‘Access-Control-Allow-Origin’ ne doit pas être le caractère générique ‘*’ lorsque le mode d'identifiants de la requête est ‘include’. »

Votre frontend envoie des cookies ou des en-têtes d'authentification avec `credentials: 'include'`, mais le serveur répond avec `Access-Control-Allow-Origin: *`. La spécification Fetch interdit cet appariement ; un caractère générique plus des identifiants permettrait à n'importe quel site sur Internet de lire les réponses authentifiées.

Solution : Renvoyez l'origine exacte au lieu du caractère générique, et ajoutez l'en-tête d'identifiants :

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

Validez l'en-tête Origin entrant par rapport à une liste blanche avant de le renvoyer. La réflexion d'origines arbitraires avec des identifiants activés annule toute la protection.

3. La réponse à la requête de pré-vérification ne passe pas la vérification de contrôle d'accès

Votre serveur n'a jamais traité la requête OPTIONS. Peut-être que la route ne définit que POST, donc OPTIONS renvoie un 404 ou 405. Peut-être qu'un middleware d'authentification l'a rejetée avec un 401 parce que la pré-vérification ne contient aucun jeton (les navigateurs n'attachent jamais d'identifiants aux pré-vérifications).

Solution : Gérez explicitement les requêtes OPTIONS et renvoyez un code 2xx avec l'ensemble complet des en-têtes CORS avant l'exécution de l'authentification. Dans la plupart des frameworks, le montage du middleware CORS en premier résout ce problème. Si vous l'écrivez à la main :

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);
});

4. La valeur de l'en-tête n'est pas égale à l'origine fournie

Le serveur envoie un en-tête Access-Control-Allow-Origin, mais il nomme une mauvaise origine. Causes courantes : une origine de production codée en dur pendant que vous testez depuis http://localhost:5173, une comparaison de liste blanche échouant sur http vs https, ou une barre oblique finale égarée (https://app.example.com/ n'est pas une valeur d'origine valide).

Solution : Comparez exactement l'en-tête Origin de la requête avec votre liste d'autorisation, renvoyez la correspondance, et envoyez Vary: Origin afin que les caches et les CDN ne servent pas l'en-tête d'une origine à une autre :

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');
}

5. Le champ d'en-tête de requête ou la méthode n'est pas autorisé

Deux messages similaires : « Le champ d'en-tête de requête authorization n'est pas autorisé par Access-Control-Allow-Headers dans la réponse preflight » et « La méthode PUT n'est pas autorisée par Access-Control-Allow-Methods. »

Le preflight a réussi, mais sa réponse ne couvrait pas ce dont votre requête avait besoin. Vous avez ajouté un en-tête Authorization ou un X-Request-Id, et la liste d'autorisation du serveur ne les a jamais mentionnés.

Solution : Étendez la réponse de preflight pour inclure tous les en-têtes et méthodes que votre frontend envoie :

Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-Id

Les noms des en-têtes ici sont insensibles à la casse. Les méthodes sont sensibles à la casse et en majuscules.

6. La redirection n'est pas autorisée pour une requête de pré-vérification

La pré-vérification a atteint une URL renvoyant un code 301 ou 302, et les navigateurs refusent de suivre les redirections pendant la pré-vérification. Les coupables typiques : une URL http redirigeant vers https, une barre oblique finale manquante que votre framework redirige « utilement », ou une passerelle renvoyant /v1/orders vers /v1/orders/.

Solution : Orientez directement votre frontend vers l'URL finale. Utilisez https dès le début, respectez la convention de la barre oblique finale de votre routeur et confirmez avec un appel OPTIONS manuel si le point de terminaison répond avec un 2xx au lieu d'un 3xx.

Exemples de configuration serveur

Voici une configuration CORS correcte dans trois piles technologiques courantes.

Express

Utilisez le middleware cors officiel au lieu de configurer les en-têtes manuellement :

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 votre middleware d'authentification afin que les requêtes de pré-vérification ne soient jamais rejetées pour des jetons manquants. Les développeurs Python suivent le même modèle avec l'extension Flask-CORS, qui encapsule une logique d'en-tête identique pour les applications Flask.

Spring Boot

Configuration globale via `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);
    }
}

Vous utilisez Spring Security ? Appelez aussi `.cors(Customizer.withDefaults())` dans votre chaîne de filtres de sécurité, sinon la couche de sécurité bloquera les pré-vérifications avant que la configuration MVC ne les voie. Consultez la documentation CORS de Spring pour l'ensemble complet des options.

Nginx

Lorsque Nginx termine les requêtes devant votre application, répondez aux pré-vérifications à 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;
}

L'indicateur `always` est important. Sans lui, Nginx supprime les directives `add_header` sur les réponses 4xx et 5xx, ce qui recrée l'erreur numéro un à chaque requête échouée. Et choisissez une seule couche pour gérer CORS : si Nginx et votre application ajoutent tous deux des en-têtes, les navigateurs voient des doublons comme `Access-Control-Allow-Origin: *, *` et rejettent la réponse.

Déboguer CORS en dehors du navigateur avec Apidog

L'erreur de console vous indique que le navigateur a bloqué quelque chose. Elle ne vous dit pas ce que le serveur a envoyé. Le moyen le plus rapide de voir la vérité est de retirer le navigateur de la boucle.

Apidog est un client API de bureau, ses requêtes ne sont donc pas soumises aux vérifications CORS du navigateur. Cela vous offre une expérience claire : envoyez la même requête depuis Apidog que celle que votre frontend effectuait. Si elle réussit là, votre logique API est correcte et le problème est purement dû à des en-têtes CORS manquants. Si elle échoue là aussi, vous avez un bogue API ordinaire déguisé en erreur CORS, et les techniques générales de test d'API s'appliquent.

Une session de débogage CORS dans Apidog ressemble à ceci :

  1. Rejouez la requête réelle. Copiez la requête échouée de l'onglet Réseau de votre navigateur et recréez-la dans Apidog avec la même méthode, les mêmes en-têtes et le même corps. Vérifiez le statut et le corps. Un 500 ici signifie que CORS n'a jamais été votre problème.
  2. Testez manuellement le preflight. Créez une nouvelle requête, définissez la méthode sur OPTIONS, et ajoutez les en-têtes qu'un navigateur enverrait : Origin: https://app.example.com, Access-Control-Request-Method: POST et Access-Control-Request-Headers: authorization, content-type. Envoyez-la.
  3. Examinez les en-têtes de réponse. Dans le panneau de réponse, recherchez Access-Control-Allow-Origin, Access-Control-Allow-Methods et Access-Control-Allow-Headers. Comparez chaque valeur avec ce dont votre frontend a besoin. Un en-tête manquant, une origine incorrecte ou un statut 3xx saute immédiatement aux yeux, sans qu'il soit nécessaire de deviner à partir de la console.
  4. Vérifiez la correction. Après avoir modifié la configuration du serveur, renvoyez la même requête OPTIONS enregistrée et observez la mise à jour des en-têtes. Pas de redéploiement de frontends, pas de rituels de nettoyage de cache.

Ce flux de travail résout également en quelques secondes l'éternel argument « fonctionne dans mon client API, échoue dans le navigateur », le même casse-tête derrière la question du test CORS de Postman. Le client fonctionne car il ignore CORS. Le navigateur échoue car votre serveur n'a pas prononcé les mots magiques. Téléchargez Apidog gratuitement et conservez la requête OPTIONS enregistrée à côté de vos tests de points de terminaison habituels ; les futurs problèmes CORS seront résolus en un clic.

Une checklist CORS en 30 secondes

Avant de signaler le bogue, parcourez cette liste :

Neuf fois sur dix, l'une de ces six lignes est votre réponse. Vérifiez-le avec une requête OPTIONS manuelle dans Apidog, corrigez la configuration du serveur et retournez au développement.

FAQ

Pourquoi n'ai-je une erreur CORS que dans le navigateur ?

Parce que seuls les navigateurs appliquent CORS. La politique de même origine protège les utilisateurs des pages malveillantes lisant leurs données authentifiées, donc les navigateurs vérifient Access-Control-Allow-Origin sur chaque réponse inter-origines. curl, les services backend et les clients de bureau n'ont pas une telle règle. Si une requête réussit partout sauf dans le navigateur, votre serveur manque ou configure mal les en-têtes CORS ; l'API elle-même est saine.

CORS s'applique-t-il à Postman ou Apidog ?

Non. Postman et Apidog sont des applications de bureau, pas des pages web exécutées dans un bac à sable de navigateur, leurs requêtes contournent donc entièrement CORS. C'est précisément ce qui les rend utiles pour le débogage CORS : ils vous montrent les en-têtes de réponse bruts du serveur sans le filtrage du navigateur. La confusion du test CORS de Postman commence généralement ici ; une requête réussie dans un client de bureau ne prouve rien sur le comportement du navigateur, mais elle isole la couche défaillante.

Une erreur CORS est-elle une fonctionnalité de sécurité ou un bogue ?

C'est une fonctionnalité. Les erreurs CORS signifient que le navigateur fait son travail : refuser d'exposer des données de réponse inter-origines à des scripts à moins que le serveur n'y consente explicitement. Désactiver CORS dans le navigateur avec des drapeaux ou des extensions masque le symptôme sur votre machine tandis que chaque utilisateur se heurte toujours au mur. Corrigez plutôt 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. Le caractère générique est rejeté chaque fois que des identifiants sont inclus, et il annonce que vos données sont ouvertes à toutes les origines sur le web. Pour tout ce qui est authentifié, maintenez une liste blanche d'origines, renvoyez l'origine correspondante et envoyez Vary: Origin afin que les caches partagés gardent les réponses séparées.

Pratiquez le Design-first d'API dans Apidog

Découvrez une manière plus simple de créer et utiliser des API