Erreur CORS Bloqué : Comprendre et Résoudre en 5 Minutes

8 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.

FAQ

CORS ne se déclenche qu'en JavaScript ?

Oui. CORS est une politique du navigateur. Les requêtes faites depuis un serveur (Node.js, Python, curl, Postman) ne sont pas soumises à CORS. C'est pour ça que ta requête fonctionne dans le terminal mais pas dans le navigateur.

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.

Pourquoi ma requête GET fonctionne mais pas mon POST ?

Un GET simple ne déclenche pas de preflight. Mais un POST avec Content-Type: application/json est considéré comme une requête "non simple" et le navigateur envoie une requête OPTIONS avant. Si ton serveur ne gère pas les OPTIONS, le POST est bloqué.

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-*.


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.