CHECKCYBER

« Blocked by CORS policy » : comprendre et corriger l’erreur

« Access to fetch at 'https://api.exemple.com' from origin 'https://site.exemple.com' has been blocked by CORS policy ». Ce message, affiché dans la console du navigateur, arrête net le développement de nombreuses applications web. Et comme la première réponse trouvée en ligne consiste presque toujours à autoriser tout le monde, l'erreur se règle souvent en créant une faille de sécurité durable.

Comprendre le mécanisme évite ce piège. La politique de même origine interdit par défaut à une page de lire la réponse d'un serveur situé sur une autre origine — c'est ce qui empêche un site malveillant de lire vos e-mails en tâche de fond pendant que vous êtes connecté. CORS est le mécanisme officiel qui permet au serveur d'autoriser explicitement certaines origines. L'erreur ne signale donc pas un bug : elle signale que le serveur n'a pas donné cette autorisation.

Ce que le message vous dit exactement

Le blocage vient du navigateur, pas du serveur : la requête est bel et bien partie, le serveur a bel et bien répondu, mais le navigateur refuse de livrer la réponse au JavaScript de la page faute d'autorisation. C'est pourquoi la requête fonctionne parfaitement avec curl ou Postman, qui n'appliquent aucune politique d'origine — un détail qui déroute beaucoup de développeurs.

Le message précise la nature du refus. « No 'Access-Control-Allow-Origin' header is present » signifie que le serveur n'a rien autorisé du tout. « The value of the header does not match the origin » indique une autorisation existante mais pour une autre origine — attention, la comparaison porte sur le schéma, le domaine et le port exacts : https://exemple.com et https://www.exemple.com sont deux origines distinctes. Enfin, « Response to preflight request doesn't pass » signale que la requête de contrôle préalable, envoyée en OPTIONS, a échoué.

Ce contrôle préalable se déclenche dès que la requête sort du cadre le plus simple : méthode PUT, PATCH ou DELETE, en-tête Authorization ou Content-Type: application/json. Le navigateur envoie alors d'abord une requête OPTIONS, à laquelle le serveur doit répondre avec les méthodes et en-têtes autorisés. Un serveur qui renvoie 404 ou 405 sur OPTIONS provoque un échec CORS même si sa configuration semble correcte par ailleurs.

La configuration correcte, côté serveur

Le principe : autorisez explicitement les origines dont vous avez besoin, et elles seules. Pour une API publique en lecture seule, sans authentification ni cookie, l'astérisque est acceptable. Dès qu'il y a authentification, il est à proscrire — et de toute façon inopérant : le navigateur refuse la combinaison de l'astérisque et des identifiants.

Pour une API authentifiée, la règle est simple : une liste blanche d'origines autorisées, une vérification que l'origine reçue en fait partie, et le renvoi de cette origine précise dans l'en-tête de réponse. Ajoutez l'en-tête Vary: Origin, sans lequel un cache ou un CDN peut servir à un visiteur la réponse autorisée pour une autre origine.

Le piège à éviter absolument est le renvoi automatique de l'origine reçue sans vérification, combiné à l'autorisation des identifiants : cela revient à autoriser n'importe quel site du web à lire les données de vos utilisateurs connectés. C'est une faille classique, régulièrement exploitée, et détectée comme telle par les audits de sécurité.

Express (Node.js) — liste blanche
const ORIGINES = ['https://site.exemple.com', 'https://admin.exemple.com'];

app.use((req, res, next) => {
  const origine = req.headers.origin;
  if (ORIGINES.includes(origine)) {
    res.setHeader('Access-Control-Allow-Origin', origine);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin');
  }
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  if (req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});
Apache — origine unique (.htaccess)
Header always set Access-Control-Allow-Origin "https://site.exemple.com"
Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
Header always append Vary "Origin"
nginx — répondre au contrôle préalable
location /api/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://site.exemple.com";
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization";
        add_header Access-Control-Max-Age 86400;
        return 204;
    }
    add_header Access-Control-Allow-Origin "https://site.exemple.com" always;
    add_header Vary Origin always;
}
PHP — liste blanche
<?php
$origines = ['https://site.exemple.com'];
$origine = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origine, $origines, true)) {
    header("Access-Control-Allow-Origin: $origine");
    header('Access-Control-Allow-Credentials: true');
    header('Vary: Origin');
}
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

En développement local : le proxy plutôt que la rustine

En local, la bonne pratique n'est pas d'ouvrir le serveur de production mais de faire passer les requêtes par le proxy de développement intégré à votre outil de build (Vite, webpack dev server, Angular CLI). Le navigateur ne voit alors qu'une seule origine, et la question CORS disparaît — sans modifier la configuration de production.

Évitez en revanche les extensions de navigateur qui « désactivent CORS » et les lancements de Chrome avec la sécurité web désactivée : ils masquent le problème sur votre poste, laissent la vraie configuration cassée pour tous les autres, et vous exposent personnellement pendant toute la session de navigation.

Vérifier la configuration

Testez le contrôle préalable directement : une requête OPTIONS envoyée avec l'en-tête Origin doit répondre 204 ou 200 et renvoyer les en-têtes d'autorisation attendus. Testez ensuite avec une origine volontairement non autorisée : le serveur ne doit alors renvoyer aucune autorisation. Si une origine arbitraire est acceptée, votre configuration est trop permissive.

Un scan externe vérifie précisément ce point parmi ses contrôles : il détecte les configurations CORS dangereuses, en particulier le renvoi automatique de l'origine avec autorisation des identifiants, et l'astérisque combiné à une API authentifiée.

Tester le contrôle préalable
curl -i -X OPTIONS https://api.exemple.com/ressource \
  -H "Origin: https://site.exemple.com" \
  -H "Access-Control-Request-Method: POST"
Vérifier qu’une origine non autorisée est refusée
curl -sI https://api.exemple.com/ressource -H "Origin: https://attaquant.example" | grep -i access-control

[ FAQ // QUESTIONS FRÉQUENTES ]

Pourquoi ça marche avec Postman mais pas dans le navigateur ?

Parce que CORS est appliqué par le navigateur, pas par le serveur. Postman et curl n'implémentent pas la politique de même origine : ils reçoivent la réponse quoi qu'il arrive. Le serveur, lui, se comporte exactement pareil dans les deux cas.

Puis-je simplement mettre Access-Control-Allow-Origin: * ?

Uniquement pour une API publique en lecture seule, sans cookie ni authentification. Dès qu'il y a des identifiants, c'est à la fois dangereux et techniquement inopérant : le navigateur rejette la combinaison de l'astérisque et des identifiants. Utilisez une liste blanche d'origines.

Qu’est-ce que la requête « preflight » ?

Une requête OPTIONS envoyée automatiquement par le navigateur avant la vraie requête, dès que celle-ci n'est pas « simple » (méthode PUT/DELETE, en-tête Authorization, Content-Type JSON). Le serveur doit y répondre avec les méthodes et en-têtes autorisés, sinon la requête réelle n'est jamais envoyée.

CORS protège-t-il mon API ?

Non, et c'est un malentendu répandu. CORS protège les utilisateurs de votre API contre des pages tierces, il ne remplace ni l'authentification, ni les contrôles d'autorisation, ni la limitation de débit — un client non navigateur ignore complètement ces règles.

[ PASSEZ À L’ACTION ]

Votre site est-il concerné ?

Lancez un audit de surface gratuit et obtenez en quelques secondes la liste exacte de vos failles, avec le correctif pour chacune. Anonyme, sans inscription.

▶ Auditer mon site

[ À LIRE AUSSI ]

[ CheckCyber // MIS À JOUR LE 2026-07-27 ]