REST API design - 12 - CORS : comprendre et debugger les erreurs cross-origin

Same-origin policy, preflight requests, Access-Control-Allow-Origin : tout ce que tu dois savoir pour que ton front parle a ton API.

REST API design 13 / 25
  1. 01 REST API design - 00 - Pourquoi le design de ton API change tout
  2. 02 REST API design - 01 - Les principes REST que personne ne lit
  3. 03 REST API design - 02 - Des URLs qui ont du sens
  4. 04 REST API design - 03 - Les méthodes HTTP, pour de vrai
  5. 05 REST API design - 04 - Les codes de statut HTTP qu'il faut connaître
  6. 06 REST API design - 05 - Body, headers et le diable dans les détails
  7. 07 REST API design - 06 - La pagination, ou comment ne pas tuer ta base
  8. 08 REST API design - 07 - Filtrage et tri sans prise de tête
  9. 09 REST API design - 08 - La validation avec Zod, gardien de ton API
  10. 10 REST API design - 09 - Erreurs : un format que tes clients vont adorer
  11. 11 REST API design - 10 - Authentification : JWT, API keys et OAuth2
  12. 12 REST API design - 11 - Versioning : quand et comment faire évoluer ton API
  13. 13 REST API design - 12 - CORS : comprendre et debugger les erreurs cross-origin
  14. 14 REST API design - 13 - Rate limiting : protéger ton API sans frustrer tes clients
  15. 15 REST API design - 14 - Caching : les bonnes réponses sont celles qu'on n'envoie pas
  16. 16 REST API design - 15 - Upload de fichiers : multipart, signed URLs et streaming
  17. 17 REST API design - 16 - Relations entre ressources : embarquer, lier ou les deux
  18. 18 REST API design - 17 - HATEOAS : des liens dans tes réponses
  19. 19 REST API design - 18 - OpenAPI : le contrat entre ton API et le monde
  20. 20 REST API design - 19 - Documentation : une API non documentee est une API inutile
  21. 21 REST API design - 20 - Testing : tester ton API sans devenir fou
  22. 22 REST API design - 21 - Webhooks : quand c'est ton API qui appelle
  23. 23 REST API design - 22 - Performance : quand chaque milliseconde compte
  24. 24 REST API design - 23 - Sécurité : les attaques que tu vas subir (et comment t'en protéger)
  25. 25 REST API design - 24 - Glossaire : tous les termes REST API en un seul endroit

12 - CORS : comprendre et debugger les erreurs cross-origin

Ce que tu vas apprendre

  • Ce qu'est la same-origin policy et pourquoi elle existe
  • Le mecanisme des preflight requests (OPTIONS)
  • Les headers CORS et comment les configurer
  • Les erreurs CORS les plus courantes et comment les résoudre

Prerequisites

Avoir lu l'article sur le versioning. Savoir ce qu'est une requête HTTP et comprendre la notion de domaine/origin.


"Access to XMLHttpRequest at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy." Si tu as deja fait du front, tu as vu ce message. Et tu as probablement installe cors avec Access-Control-Allow-Origin: * en te disant "ca marche, on verra plus tard". On va voir maintenant.

La same-origin policy

Le navigateur interdit par défaut les requêtes vers un domaine différent de celui de la page. https://app.example.com ne peut pas appeler https://api.example.com. C'est la same-origin policy.

Deux URLs ont la meme origin si elles partagent le meme protocole, domaine et port :

https://app.example.com:443  -- origin
https://app.example.com:443/users  -- meme origin
http://app.example.com:443   -- different (protocole)
https://api.example.com:443  -- different (domaine)
https://app.example.com:8080 -- different (port)

Cette restriction existe pour empecher un site malveillant de faire des requêtes vers ta banque en utilisant tes cookies. C'est une protection du navigateur, pas du serveur. Un curl ou un appel serveur-to-serveur n'est pas concerne.

CORS : la soupape

CORS (Cross-Origin Resource Sharing) est le mecanisme qui permet au serveur de dire "j'autorise les requêtes venant de tel origin". Le serveur ajoute des headers dans sa réponse :

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

Le navigateur lit ces headers et décidé s'il laisse passer la réponse ou s'il la bloque.

Les preflight requests

Pour certaines requêtes (PUT, DELETE, ou toute requête avec un header custom comme Authorization), le navigateur envoie d'abord une requête OPTIONS pour demander la permission :

OPTIONS /users/42 HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization

Le serveur répond :

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: DELETE
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 86400

Access-Control-Max-Age dit au navigateur de cacher cette réponse pendant 24 heures. Sans ca, chaque requête généré un preflight supplementaire, ce qui double le nombre de requêtes.

Les requêtes "simples" (GET, POST avec Content-Type: application/x-www-form-urlencoded) ne declenchent pas de preflight. Mais des que tu ajoutes Content-Type: application/json ou Authorization, le preflight se déclenché.

Configuration en Express

typescriptimport cors from "cors";

// Dev : tout autoriser
app.use(cors());

// Production : origin specifique
app.use(cors({
  origin: "https://app.paltemps.fr",
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400
}));

Pour plusieurs origins :

typescriptconst allowedOrigins = [
  "https://app.example.com",
  "https://admin.example.com"
];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error("CORS not allowed"));
    }
  },
  credentials: true
}));

Le !origin gere les requêtes sans origin (curl, Postman, requêtes serveur-to-serveur).

Configuration en Elysia

typescriptimport { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";

new Elysia()
  .use(cors({
    origin: "https://app.example.com",
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
    credentials: true
  }))
  .listen(3000);

Wildcard vs origin spécifique

Access-Control-Allow-Origin: * autorise tout le monde. Pratique en dev, dangereux en prod. Et surtout : ca ne marche pas avec credentials: true. Si tu as besoin d'envoyer des cookies ou le header Authorization, tu dois spécifier l'origin exacte.

# Ca ne marche PAS ensemble
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Le navigateur refusera la réponse. C'est une des erreurs CORS les plus frequentes.

Les erreurs courantes et leurs solutions

"No 'Access-Control-Allow-Origin' header" : ton serveur ne renvoie pas le header. Verifie que le middleware CORS est bien charge avant tes routes.

"The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*'" : tu utilises * avec credentials: true. Specifie l'origin exacte.

"Request header field authorization is not allowed" : tu n'as pas déclaré Authorization dans Access-Control-Allow-Headers.

La requête OPTIONS retourne 404 : ton framework ne gere pas automatiquement les OPTIONS. Ajoute un handler explicite ou vérifié la configuration du middleware.

Ca marche en Postman mais pas dans le navigateur : normal. Postman n'applique pas la same-origin policy. C'est une protection navigateur uniquement.

Un piège avec les proxys

En développement, beaucoup de frameworks front (Next.js, Vite) proposent un proxy intégré qui redirige /api/* vers ton backend. Ca elimine les problèmes CORS en dev puisque le front et l'API sont sur la meme origin (localhost). Mais en production, le proxy n'existe plus et CORS revient. Teste toujours ta configuration CORS dans un environnement proche de la prod.

Résumé

  • CORS est un mecanisme navigateur qui permet au serveur d'autoriser les requêtes cross-origin
  • Les preflight requests (OPTIONS) verifient les permissions avant la vraie requête
  • Access-Control-Allow-Origin: * ne fonctionne pas avec credentials: true
  • Configure Access-Control-Max-Age pour éviter les preflight a chaque requête
  • Teste ta config CORS en dehors de Postman, qui ignore la same-origin policy

Article précédent : Versioning Article suivant : Rate limiting

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.