DevToolBoxGRATUIT
Blog

Comment corriger les erreurs CORS : Guide complet de dépannage

12 min de lecturepar DevToolBox

Les erreurs CORS sont l'un des problèmes les plus frustrants du développement web. Vous voyez le redoutable message rouge dans la console, votre appel API échoue, et rien ne semble fonctionner. Ce guide vous aidera à comprendre exactement ce qu'est CORS, diagnostiquer votre erreur et la corriger.

Qu'est-ce que CORS ? (Explication en 30 secondes)

Le Cross-Origin Resource Sharing (CORS) est une fonctionnalité de sécurité du navigateur qui bloque les pages web qui tentent d'envoyer des requêtes vers un domaine différent de celui qui sert la page. Quand votre frontend http://localhost:3000 essaie de récupérer des données depuis http://api.example.com, le navigateur vérifie si le serveur API autorise explicitement cette requête cross-origin via des en-têtes HTTP spéciaux.

Comment fonctionne CORS : le flux

┌─────────────────┐         ┌─────────────────────┐
│   Browser        │         │   API Server         │
│   (Frontend)     │         │   (Backend)          │
│                  │         │                      │
│ http://localhost │ ──────> │ https://api.example  │
│     :3000        │  fetch  │     .com             │
│                  │         │                      │
└─────────────────┘         └─────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    │                               │
              CORS Headers                   No CORS Headers
              Present?                       Present?
                    │                               │
                    ▼                               ▼
          ┌─────────────────┐            ┌──────────────────┐
          │  Response sent  │            │  Browser BLOCKS  │
          │  to JavaScript  │            │  the response    │
          └─────────────────┘            └──────────────────┘

Quand CORS est correctement configuré, le serveur répond avec des en-têtes qui disent au navigateur : "Oui, j'autorise les requêtes de cette origine."

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json

{"data": "success"}

Quand CORS n'est PAS configuré, le navigateur bloque la réponse (même si le serveur a traité la requête avec succès).

HTTP/1.1 200 OK
Content-Type: application/json
# No Access-Control-Allow-Origin header!

{"data": "success"}  <-- Browser received this but BLOCKS JavaScript from reading it

Les 5 messages d'erreur CORS les plus courants

Erreur 1 : En-tête Access-Control-Allow-Origin absent

No 'Access-Control-Allow-Origin' header is present on the requested resource.

Signification : Le serveur n'a pas inclus l'en-tête Access-Control-Allow-Origin. C'est l'erreur CORS la plus courante.

Erreur 2 : Wildcard avec identifiants

The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include'.

Signification : Vous envoyez des cookies ou des en-têtes Authorization, mais le serveur répond avec *. Avec les identifiants, le serveur doit spécifier l'origine exacte.

Erreur 3 : Requête preflight échouée

Response to preflight request doesn't pass access control check.

Signification : Le navigateur a envoyé une requête OPTIONS (preflight) et le serveur n'a pas géré correctement cette requête.

Erreur 4 : Méthode non autorisée

Method PUT is not allowed by Access-Control-Allow-Methods.

Signification : L'en-tête Access-Control-Allow-Methods du serveur ne contient pas la méthode HTTP utilisée.

Erreur 5 : En-tête non autorisé

Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers.

Signification : Le serveur n'a pas autorisé l'en-tête personnalisé dans Access-Control-Allow-Headers.

Organigramme de diagnostic : où est le problème ?

Utilisez cet arbre de décision pour identifier rapidement si votre problème CORS est frontend, backend ou proxy :

Étape 1 : Ouvrez DevTools > onglet Network. Voyez-vous une requête OPTIONS avant votre requête ?

OUI -> Le navigateur envoie une requête preflight. Vérifiez si la réponse OPTIONS a le statut 200 et contient les en-têtes CORS.

NON -> C'est une requête simple. Le problème est l'absence d'en-têtes CORS dans la réponse du serveur.

Étape 2 : L'erreur mentionne-t-elle "preflight" ?

OUI -> Problème backend. Votre serveur doit gérer les requêtes OPTIONS.

NON -> Vérifiez si vous utilisez des identifiants. Si oui, vous ne pouvez pas utiliser le wildcard (*).

Étape 3 : L'API fonctionne-t-elle en appel direct (curl, Postman) ?

OUI -> C'est bien un problème CORS (navigateur uniquement).

NON -> Ce n'est PAS un problème CORS. L'API elle-même a un problème.

Résumé : 90% des erreurs CORS se résolvent en configurant les bons en-têtes sur votre serveur backend.

Corrections côté serveur par framework

Express.js (Node.js)

// Method 1: Using the cors package (recommended)
npm install cors

const express = require('express');
const cors = require('cors');
const app = express();

// Allow specific origins
app.use(cors({
  origin: ['http://localhost:3000', 'https://yoursite.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400 // Cache preflight for 24 hours
}));

// Method 2: Manual middleware
app.use((req, res, next) => {
  const allowedOrigins = ['http://localhost:3000', 'https://yoursite.com'];
  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,PATCH');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');

  // Handle preflight
  if (req.method === 'OPTIONS') {
    res.setHeader('Access-Control-Max-Age', '86400');
    return res.status(204).end();
  }
  next();
});

Django (Python)

# Install django-cors-headers
pip install django-cors-headers

# settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # Must be FIRST
    'django.middleware.common.CommonMiddleware',
    ...
]

# Option 1: Allow specific origins
CORS_ALLOWED_ORIGINS = [
    'http://localhost:3000',
    'https://yoursite.com',
]

# Option 2: Allow all origins (dev only!)
# CORS_ALLOW_ALL_ORIGINS = True

# Allow credentials (cookies, auth headers)
CORS_ALLOW_CREDENTIALS = True

# Allowed headers
CORS_ALLOW_HEADERS = [
    'content-type',
    'authorization',
    'x-requested-with',
]

# Allowed methods
CORS_ALLOW_METHODS = [
    'GET',
    'POST',
    'PUT',
    'PATCH',
    'DELETE',
    'OPTIONS',
]

Flask (Python)

# Install flask-cors
pip install flask-cors

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Option 1: Allow specific origins
CORS(app, origins=['http://localhost:3000', 'https://yoursite.com'],
     supports_credentials=True,
     allow_headers=['Content-Type', 'Authorization'],
     methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])

# Option 2: Manual approach
@app.after_request
def after_request(response):
    origin = request.headers.get('Origin')
    allowed = ['http://localhost:3000', 'https://yoursite.com']
    if origin in allowed:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
        response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

Go (net/http)

package main

import (
    "net/http"
    "strings"
)

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        allowedOrigins := map[string]bool{
            "http://localhost:3000": true,
            "https://yoursite.com": true,
        }

        origin := r.Header.Get("Origin")
        if allowedOrigins[origin] {
            w.Header().Set("Access-Control-Allow-Origin", origin)
            w.Header().Set("Access-Control-Allow-Methods",
                strings.Join([]string{"GET","POST","PUT","DELETE","PATCH"}, ","))
            w.Header().Set("Access-Control-Allow-Headers",
                "Content-Type, Authorization")
            w.Header().Set("Access-Control-Allow-Credentials", "true")
            w.Header().Set("Access-Control-Max-Age", "86400")
        }

        // Handle preflight
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }

        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/data", handleData)

    handler := corsMiddleware(mux)
    http.ListenAndServe(":8080", handler)
}

Configuration Nginx

server {
    listen 80;
    server_name api.example.com;

    # CORS headers for all locations
    set $cors_origin "";
    if ($http_origin ~* "^https?://(localhost:3000|yoursite\.com)$") {
        set $cors_origin $http_origin;
    }

    location /api/ {
        # CORS headers
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 86400 always;

        # Handle preflight requests
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' $cors_origin always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Max-Age' 86400;
            add_header 'Content-Length' 0;
            add_header 'Content-Type' 'text/plain';
            return 204;
        }

        proxy_pass http://backend:8080;
    }
}

Proxy inverse comme middleware CORS

Si vous ne pouvez pas modifier l'API backend (API tierce), utilisez un proxy inverse pour ajouter les en-têtes CORS.

Proxy inverse Nginx

# Nginx as CORS proxy for a third-party API
server {
    listen 80;
    server_name cors-proxy.yoursite.com;

    location /proxy/ {
        # Add CORS headers
        add_header 'Access-Control-Allow-Origin' 'https://yoursite.com' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;

        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'https://yoursite.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;
        }

        # Strip /proxy/ prefix and forward to third-party API
        rewrite ^/proxy/(.*) /$1 break;
        proxy_pass https://third-party-api.com;
        proxy_set_header Host third-party-api.com;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Usage: fetch('https://cors-proxy.yoursite.com/proxy/endpoint')

Proxy inverse Apache

# Apache as CORS proxy
<VirtualHost *:80>
    ServerName cors-proxy.yoursite.com

    # Enable required modules
    # a2enmod proxy proxy_http headers rewrite

    <Location "/proxy/">
        Header always set Access-Control-Allow-Origin "https://yoursite.com"
        Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
        Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
        Header always set Access-Control-Allow-Credentials "true"

        # Handle preflight
        RewriteEngine On
        RewriteCond %{REQUEST_METHOD} OPTIONS
        RewriteRule ^(.*)$ $1 [R=204,L]

        # Proxy to backend
        ProxyPass "http://backend:8080/"
        ProxyPassReverse "http://backend:8080/"
    </Location>
</VirtualHost>

Solutions pour le développement uniquement

Ces solutions sont uniquement pour le développement local. Ne jamais les utiliser en production.

Proxy Vite

Le proxy intégré de Vite redirige les requêtes du serveur de développement vers l'API, évitant CORS car le navigateur ne voit que des requêtes same-origin :

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

export default defineConfig({
  server: {
    proxy: {
      // Proxy /api requests to backend
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        // Optional: rewrite path
        // rewrite: (path) => path.replace(/^\/api/, ''),
      },
      // Proxy with WebSocket support
      '/ws': {
        target: 'ws://localhost:8080',
        ws: true,
      },
    },
  },
});

// In your code, use relative URLs:
// fetch('/api/users')  instead of  fetch('http://localhost:8080/api/users')

Proxy Webpack devServer

Create React App et les setups webpack supportent un proxy similaire :

// package.json (Create React App)
{
  "proxy": "http://localhost:8080"
}

// Or for more control, create src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:8080',
      changeOrigin: true,
    })
  );
};

// webpack.config.js (manual setup)
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      },
    },
  },
};

Rewrites Next.js

Next.js peut proxifier les requêtes API via son propre serveur avec les rewrites :

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:8080/api/:path*',
      },
    ];
  },
};

module.exports = nextConfig;

// In your code:
// fetch('/api/users')  -- proxied to http://localhost:8080/api/users

Pièges courants et cas particuliers

Piège 1 : Wildcard (*) avec identifiants

Quand le frontend envoie des identifiants, le serveur NE PEUT PAS utiliser *. Il doit renvoyer l'origine exacte :

FAUX (échouera avec les identifiants) :

// Frontend
fetch('https://api.example.com/data', {
  credentials: 'include'  // Sending cookies
});

// Backend response header:
Access-Control-Allow-Origin: *   // FAILS! Cannot use * with credentials

CORRECT (origine dynamique pour les identifiants) :

// Backend (Express.js example)
app.use((req, res, next) => {
  // Echo back the exact origin
  const origin = req.headers.origin;
  const allowedOrigins = ['http://localhost:3000', 'https://yoursite.com'];

  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);  // Exact origin
  }
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});

Piège 2 : Absence de gestionnaire OPTIONS (preflight)

Les requêtes non-simples déclenchent un preflight OPTIONS. Si votre serveur renvoie 404 ou 405 pour OPTIONS, CORS échoue.

TriggerExample
Custom headersAuthorization, X-Request-ID, X-API-Key
Non-simple methodsPUT, DELETE, PATCH
Non-simple Content-Typeapplication/json, application/xml
ReadableStream bodyStreaming request body
// Ensure your server handles OPTIONS for all API routes:
app.options('*', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
  res.setHeader('Access-Control-Max-Age', '86400');
  res.status(204).end();
});

Piège 3 : Cookies et SameSite

Les cookies cross-origin nécessitent trois conditions :

// 1. Frontend: include credentials
fetch('https://api.example.com/data', {
  credentials: 'include'
});

// 2. Backend: allow credentials + exact origin
res.setHeader('Access-Control-Allow-Origin', 'https://yoursite.com');  // NOT *
res.setHeader('Access-Control-Allow-Credentials', 'true');

// 3. Cookie must have correct attributes
Set-Cookie: session=abc123; SameSite=None; Secure; HttpOnly; Path=/

// IMPORTANT: SameSite=None requires Secure (HTTPS).
// This means cross-origin cookies do NOT work on HTTP (localhost).
// For local dev, use a proxy instead of cross-origin cookies.

Piège 4 : Cache des réponses preflight

Les requêtes preflight peuvent être lentes. Utilisez Access-Control-Max-Age pour les mettre en cache :

// Server response header:
Access-Control-Max-Age: 86400  // Cache preflight for 24 hours

// Without this header, the browser sends a preflight OPTIONS
// request before EVERY non-simple request, adding latency.

// With caching, the browser remembers the CORS permissions
// and skips the preflight for subsequent requests.

Note : Chrome limite à 7200 secondes (2 heures). Firefox limite à 86400 (24 heures).

Piège 5 : CORS et redirections

Si votre endpoint API redirige (301/302), les en-têtes CORS doivent être présents sur les DEUX réponses. Évitez les redirections dans les endpoints API.

// PROBLEM: API endpoint redirects
// GET https://api.example.com/users -> 301 -> https://api.example.com/v2/users
// CORS headers must be on BOTH responses!

// SOLUTION: Avoid redirects in API endpoints
// Option 1: Update the frontend URL
fetch('https://api.example.com/v2/users');  // Use final URL directly

// Option 2: Make the redirect include CORS headers
// (requires server configuration on both the old and new endpoint)

Référence rapide : en-têtes CORS

En-têteObjectifValeur exemple
Access-Control-Allow-OriginWhich origins can access the resourcehttps://yoursite.com or *
Access-Control-Allow-MethodsWhich HTTP methods are allowedGET, POST, PUT, DELETE
Access-Control-Allow-HeadersWhich request headers are allowedContent-Type, Authorization
Access-Control-Allow-CredentialsWhether cookies/auth can be senttrue
Access-Control-Max-AgeHow long to cache preflight (seconds)86400
Access-Control-Expose-HeadersWhich response headers JS can readX-Total-Count, X-Request-ID

Questions fréquemment posées

Peut-on désactiver CORS dans le navigateur ?

Techniquement oui, en lançant Chrome avec --disable-web-security, mais c'est extrêmement dangereux. Pour le développement, utilisez un proxy (Vite, webpack).

Pourquoi mon API fonctionne dans Postman mais pas dans le navigateur ?

CORS est un mécanisme de sécurité du navigateur uniquement. Postman, curl et les requêtes serveur-à-serveur n'appliquent pas CORS.

CORS est-il un correctif frontend ou backend ?

Presque toujours backend. Le serveur doit envoyer les bons en-têtes CORS. Le frontend ne peut pas contourner CORS. La seule solution frontend est d'utiliser un proxy.

Faut-il utiliser Access-Control-Allow-Origin: * en production ?

Uniquement pour les API vraiment publiques sans cookies ni authentification. Pour les API avec identifiants, spécifiez les origines exactes.

Qu'est-ce qu'une requête preflight ?

C'est une requête OPTIONS automatique envoyée par le navigateur avant la requête réelle. Elle se produit avec des en-têtes personnalisés, des méthodes non-simples ou un Content-Type spécial.

Comment corriger CORS pour les connexions WebSocket ?

Les WebSocket ne suivent pas les règles CORS. Le serveur doit valider l'en-tête Origin manuellement. Si une erreur CORS apparaît, c'est généralement le endpoint HTTP initial.

𝕏 Twitterin LinkedIn
Cet article vous a-t-il aidé ?

Restez informé

Recevez des astuces dev et les nouveaux outils chaque semaine.

Pas de spam. Désabonnez-vous à tout moment.

Essayez ces outils associés

🛡️CSP Header GeneratorNXNginx Config Generator.ht.htaccess Generator4xxHTTP Status Code Reference

Articles connexes

REST API Best Practices : Le guide complet pour 2026

Apprenez les meilleures pratiques de conception REST API : conventions de nommage, gestion des erreurs, authentification, pagination et sécurité.

Content Security Policy (CSP) : Guide complet du débutant à la production

Apprenez le CSP de zéro : toutes les directives, configurations courantes, reporting et déploiement.