Erreur CORS en JavaScript : causes et solution rapide

12 min de lecture JavaScript

Tu fais un fetch() vers une API et le navigateur te répond avec une grosse erreur rouge dans la console : "Access to fetch has been blocked by CORS policy". Pas de panique. CORS (Cross-Origin Resource Sharing) est un mécanisme de sécurité du navigateur qui contrôle quels sites peuvent appeler quels serveurs. En 5 minutes, tu vas comprendre pourquoi ça bloque et comment le résoudre.

Pourquoi le navigateur bloque ta requête

La same-origin policy en 30 secondes

Par défaut, un navigateur applique la same-origin policy : une page web ne peut faire des requêtes HTTP qu'à la même origine (même protocole + même domaine + même port). Dès qu'un seul de ces trois éléments diffère, c'est une requête cross-origin.

Même origine :
https://monsite.com  →  https://monsite.com/api/users

Origines différentes (CORS nécessaire) :
https://monsite.com       →  https://api.monsite.com    (sous-domaine différent)
http://localhost:5173     →  http://localhost:3000       (port différent)
https://monsite.com       →  https://autre-api.com      (domaine différent)

Quand CORS se déclenche

Chaque fois que ton code JavaScript fait un fetch(), un XMLHttpRequest, ou appelle une API depuis le navigateur vers une autre origine, le navigateur vérifie que le serveur autorise explicitement cette requête via des headers CORS. Si le serveur ne renvoie pas les bons headers, le navigateur bloque la réponse avant même que ton code puisse la lire.

Point important : CORS est une protection côté navigateur, pas côté serveur. Le serveur reçoit bien ta requête et renvoie bien une réponse. Mais c'est le navigateur qui décide de te la montrer ou non. C'est pour ça que la même requête fonctionne parfaitement avec curl ou Postman, mais échoue dans le navigateur.

Les 3 erreurs CORS les plus fréquentes

1. "No 'Access-Control-Allow-Origin' header"

C'est l'erreur la plus courante. Tu la verras sous cette forme dans la console :

Access to fetch at 'https://api.example.com/data' from origin
'http://localhost:5173' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the
requested resource.

Pourquoi ? Le serveur ne renvoie pas le header Access-Control-Allow-Origin dans sa réponse. Le navigateur ne sait donc pas si ton site est autorisé à lire la réponse.

Solution : Le serveur doit ajouter ce header à sa réponse :

# Autoriser une origine spécifique
Access-Control-Allow-Origin: https://monsite.com

# Ou autoriser toutes les origines (dev uniquement)
Access-Control-Allow-Origin: *

2. "CORS preflight request failed"

Cette erreur apparaît quand tu envoies une requête "non simple" (POST avec Content-Type: application/json, requête avec des headers custom, PUT, DELETE, etc.). Le navigateur envoie d'abord une requête OPTIONS (le "preflight") pour demander au serveur s'il accepte ce type de requête.

Access to fetch at 'https://api.example.com/users' from origin
'http://localhost:5173' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check.

Pourquoi ? Le serveur ne gère pas les requêtes OPTIONS ou ne renvoie pas les headers nécessaires pour le preflight.

Solution : Le serveur doit répondre à la requête OPTIONS avec les bons headers :

Access-Control-Allow-Origin: https://monsite.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

3. "Credentials flag is true but Access-Control-Allow-Credentials is not true"

Cette erreur apparaît quand tu envoies des cookies ou un header Authorization avec ta requête :

// Ce code déclenche l'erreur si le serveur n'est pas configuré
fetch('https://api.example.com/profile', {
  credentials: 'include' // envoie les cookies
})
Access to fetch at 'https://api.example.com/profile' from origin
'http://localhost:5173' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Credentials' header in
the response is '' which must be 'true' when the request's
credentials mode is 'include'.

Pourquoi ? Quand tu utilises credentials: 'include', le serveur doit explicitement autoriser les credentials ET ne peut plus utiliser le wildcard * pour l'origine.

Solution : Le serveur doit renvoyer :

Access-Control-Allow-Origin: https://monsite.com  # PAS *
Access-Control-Allow-Credentials: true

Comment corriger côté serveur (Node/Express)

Méthode 1 : Le middleware cors (recommandé)

La solution la plus propre avec Express :

npm install cors
import express from 'express';
import cors from 'cors';

const app = express();

// Autoriser une origine spécifique
app.use(cors({
  origin: 'https://monsite.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true // si tu utilises des cookies
}));

// Ou autoriser plusieurs origines
app.use(cors({
  origin: ['https://monsite.com', 'http://localhost:5173'],
}));

app.get('/api/data', (req, res) => {
  res.json({ message: 'CORS configuré !' });
});

app.listen(3000);

Méthode 2 : Headers manuels

Si tu ne veux pas de dépendance externe, tu peux ajouter les headers manuellement :

app.use((req, res, next) => {
  const allowedOrigins = ['https://monsite.com', 'http://localhost:5173'];
  const origin = req.headers.origin;
  
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  
  // Répondre immédiatement aux requêtes preflight
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  
  next();
});

Solutions côté front (quand tu ne contrôles pas le serveur)

Parfois, tu appelles une API tierce que tu ne peux pas modifier. Voici comment contourner le problème proprement.

Proxy en développement (Vite)

En dev, configure un proxy dans vite.config.js pour que les requêtes passent par ton serveur de dev (qui n'est pas soumis à CORS) :

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'https://api-externe.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

Ensuite dans ton code, tu appelles /api/endpoint au lieu de https://api-externe.com/endpoint :

// Avant (bloqué par CORS)
fetch('https://api-externe.com/data');

// Après (passe par le proxy Vite, pas de CORS)
fetch('/api/data');

Proxy en production (API route)

En production, le proxy Vite n'existe plus. Tu dois créer ta propre route côté serveur qui relaie les requêtes :

// Sur ton serveur Express/Node
app.get('/api/proxy/data', async (req, res) => {
  try {
    const response = await fetch('https://api-externe.com/data', {
      headers: {
        'Authorization': `Bearer ${process.env.API_KEY}`
      }
    });
    const data = await response.json();
    res.json(data);
  } catch (error) {
    res.status(500).json({ error: 'Erreur proxy' });
  }
});

Avantage : tes clés API restent côté serveur et ne sont jamais exposées dans le navigateur.

Ce qu'il ne faut PAS faire

Désactiver la sécurité du navigateur

Tu trouveras des tutos qui te disent de lancer Chrome avec --disable-web-security. Ne fais jamais ça. Tu désactives toute la protection CORS pour tous les sites, y compris tes sessions bancaires et tes mails. C'est un risque de sécurité majeur, même "juste en dev".

# NE FAIS PAS CA
chrome --disable-web-security --user-data-dir=/tmp/chrome-test

Utiliser un proxy CORS public

Des services comme cors-anywhere ou d'autres proxy CORS publics existent. Le problème :

// NE FAIS PAS CA en production
fetch('https://cors-anywhere.herokuapp.com/https://api.example.com/data')

La bonne solution est toujours de configurer CORS côté serveur ou de créer ton propre proxy.

Diagnostic rapide : mauvaise configuration CORS, preflight ou credentials ?

Si tu vois Cross-Origin Request Blocked, une erreur sur No Access-Control-Allow-Origin ou un preflight OPTIONS qui casse, tu n as pas juste un bug fetch(). Tu es presque toujours face a une mauvaise configuration CORS cote API, proxy local ou credentials.

Message vu Cause probable Correctif prioritaire
No Access-Control-Allow-Origin Header absent dans la reponse API Ajouter Access-Control-Allow-Origin cote serveur
Preflight request doesn't pass access control check OPTIONS non gere ou headers incomplets Renvoyer Access-Control-Allow-Methods et Access-Control-Allow-Headers
Credentials mode is include Cookies actifs avec wildcard * Renvoyer l origine exacte + Access-Control-Allow-Credentials: true

Tester ta configuration avec l outil CORS GoGoKodo

Tu veux aller plus vite qu une lecture theorique ? Utilise le testeur CORS GoGoKodo. Tu colles ton origine frontend, l URL API, les headers envoyes et le message de console. L outil te dit si ton blocage vient d un header manquant, d un preflight OPTIONS ou d un conflit entre credentials et wildcard.

Il genere aussi des pistes concretes pour Express, Vite proxy et un proxy backend, ce qui est souvent le plus rapide pour depanner un projet front + API.

Si ton erreur survient côté serveur Node.js plutôt que dans le navigateur, il s'agit probablement d'un autre problème : consulte fetch is not defined : ReferenceError en Node.js pour la solution.

FAQ

Pourquoi mon fetch est bloqué par CORS ?

Parce que ta page (origine A) appelle une API hébergée sur une autre origine (B) et que le navigateur applique la same-origin policy. Si la réponse de l'API ne contient pas le header Access-Control-Allow-Origin autorisant ton origine, le navigateur bloque l'accès au résultat — même si la requête a bien atteint le serveur. Ce n'est pas fetch qui échoue, c'est la lecture de la réponse qui est interdite.

Comment corriger une mauvaise configuration CORS côté serveur ?

Ajoute les bons headers dans les réponses de ton API. En Express, le plus simple est le middleware cors : app.use(cors({ origin: 'https://ton-front.com' })). En nginx, ajoute add_header Access-Control-Allow-Origin "https://ton-front.com"; et gère les requêtes OPTIONS avec un return 204. Renvoie aussi Access-Control-Allow-Methods et Access-Control-Allow-Headers si tu utilises des méthodes ou headers personnalisés.

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

CORS est une politique appliquée uniquement par le navigateur. Postman, curl, Node.js ou Python ne sont pas soumis à la same-origin policy : ils reçoivent la réponse normalement. Le navigateur, lui, vérifie les headers CORS avant de te laisser lire la réponse. Si ça marche dans Postman mais pas dans ton app, le problème vient bien des headers CORS, pas de ton code fetch.

C'est quoi une requête preflight OPTIONS ?

C'est une requête OPTIONS que le navigateur envoie automatiquement avant ta vraie requête, pour demander au serveur s'il autorise l'opération. Elle se déclenche pour les requêtes "non simples" : méthode PUT/DELETE, header Content-Type: application/json, ou headers personnalisés. Le serveur doit y répondre avec Access-Control-Allow-Methods et Access-Control-Allow-Headers. Si le preflight échoue, ta vraie requête n'est jamais envoyée.

Comment autoriser plusieurs origines ?

Tu ne peux pas mettre plusieurs valeurs dans Access-Control-Allow-Origin (sauf *). La bonne pratique : maintenir une liste blanche d'origines autorisées et renvoyer dynamiquement l'origine de la requête si elle y figure. En Express : cors({ origin: ['https://app.com', 'https://admin.app.com'] }) gère ça pour toi. En manuel, lis le header Origin de la requête, vérifie qu'il est dans ta liste, puis renvoie-le tel quel dans Access-Control-Allow-Origin.

Est-ce que Access-Control-Allow-Origin: * est dangereux ?

Pour une API publique en lecture seule (météo, données ouvertes), c'est acceptable. Mais si ton API utilise des cookies ou de l'authentification, le wildcard * est insuffisant : il est incompatible avec credentials: 'include' et ne protège pas tes endpoints sensibles. Utilise une origine spécifique.

Comment débuguer une erreur CORS ?

Ouvre l'onglet Network des DevTools. Cherche la requête qui échoue. Si tu vois une requête OPTIONS avec un status 405 ou sans les headers CORS, c'est le preflight qui échoue. Vérifie que ton serveur répond aux OPTIONS avec les bons headers Access-Control-Allow-*. Tu peux aussi utiliser le testeur CORS GoGoKodo pour isoler la cause en quelques secondes.


Si tu débutes en JavaScript et que tu veux comprendre les bases avant de t'attaquer aux API, consulte le guide complet JavaScript pour débutant. Pour maîtriser les appels asynchrones comme fetch, jette un oeil à comprendre async/await et les Promises. Et si tu veux éviter les pièges classiques, voici les erreurs JavaScript de débutant les plus courantes.

Tu veux pratiquer JavaScript avec des exercices interactifs ? Essaie les ateliers de code GoGoKodo : tu codes directement dans le navigateur avec correction instantanée.