Débogueur CORS

Récupère les vrais response headers de n'importe quelle URL, vois si un preflight entre en jeu, repère la chose exacte qui casse et copie un correctif de config serveur.

Ce débogueur CORS traque vite fait une panne cross-origin, le genre qui vire la console au rouge juste après un déploiement. Pointe-le sur une URL cible, dis-lui quel origin passe les appels, règle la méthode, le mode credentials et tes éventuels headers personnalisés, et il récupère les vrais response headers, détermine si un preflight entre seulement en jeu, puis nomme la règle exacte de la spec qui casse. Il attrape les suspects habituels : un wildcard associé à des credentials, un handler OPTIONS manquant, un header jamais arrivé dans Allow-Headers, un slash final qui casse une comparaison d'origin octet par octet, un Vary: Origin absent qui empoisonne un cache. Ensuite il te tend une config prudente en forme d'allow-list à coller direct dans nginx, Apache, Express, FastAPI ou Django, avec un 204 propre sur le preflight. Tes origins ne quittent jamais la page.

Les requêtes passent par le service de lookup PeopleAreGeek. Nous ne journalisons rien.

Testeur de preflight CORS, diagnostic des response headers et générateur de config pour nginx, Apache, Express et FastAPI

CORS m'a tiré du lit à 2 h du matin. Un déploiement part. La console vire au rouge. Le front n'arrive tout simplement plus à joindre l'API, et te voilà à plisser les yeux sur un header en pleine nuit. Du coup j'ai construit ça. Tu lui pointes une URL, tu indiques quel origin passe les appels, et il récupère les vrais response headers, détermine si un preflight entre seulement en jeu, puis nomme la chose exacte qui casse et te tend la config à coller dans ton serveur. Tes origins ne quittent jamais la page.

Entre une URL cible et un origin appelant, puis clique sur « Déboguer CORS ».

Ce que CORS vérifie vraiment, et l'endroit où la plupart des équipes trébuchent

Ce débogueur CORS tranche la question qui dévore des après-midi entiers : pourquoi une requête marche partout sauf dans le navigateur. CORS décide si un script sur un origin a le droit de lire une réponse venant d'un autre origin, et le navigateur est la seule chose qui prend cette décision. Pas ton serveur, et c'est honnêtement le bout qui embrouille tout le monde. C'est pour ça que curl aspire la même URL sans accroc pendant que le navigateur te claque la porte au nez : même réponse, arbitre différent. Et CORS n'empêche pas la requête de partir. Le navigateur l'envoie, ton serveur l'exécute, la réponse revient, et c'est seulement là que le navigateur cache cette réponse au JavaScript qui l'avait demandée. Donc au moment où « blocked by CORS » atterrit dans ta console, tout ce que cette requête a fait à ton serveur, c'est déjà fait. Relis ça deux fois si tu balances un DELETE.

Les trois coupables que cet outil détecte

  • Wildcard plus credentials : associer Access-Control-Allow-Origin: * à Access-Control-Allow-Credentials: true est purement et simplement illégal selon la spec, et tous les navigateurs modernes le refusent. Vire l'étoile et renvoie en écho le véritable origin demandeur à la place.
  • Handler de preflight manquant : toute requête non-simple (un PUT, un DELETE, un Content-Type custom, un header custom) déclenche un preflight OPTIONS avant le vrai appel, et ton serveur doit y répondre avec les bons Allow-Methods et Allow-Headers. La plupart des frameworks ne te le servent pas gratuitement.
  • Preflight mis en cache : Access-Control-Max-Age dit au navigateur de retenir le résultat du preflight, donc tu corriges ta config et rien ne change tant que ce cache n'a pas expiré, et te voilà planté là à te demander pourquoi le correctif n'a rien fait.

Les snippets de config serveur et ce contre quoi ils protègent

Prends la stack sur laquelle tu es. nginx, Apache, Express, FastAPI, Django : la config que l'outil te renvoie garde la même forme prudente, peu importe. Elle renvoie en écho l'origin de la requête, mais uniquement quand cet origin figure sur une allow-list explicite. Elle pose Vary: Origin pour qu'un cache ne puisse pas refiler la réponse d'un appelant à quelqu'un d'autre. Elle répond au preflight OPTIONS avec un 204 propre, et elle reste bien loin du wildcard. Une allow-list, c'est juste le bon défaut aujourd'hui. Si ton serveur encaisse des appels cross-origin venant d'un ou deux fronts que tu possèdes réellement, il n'a rien à faire à renvoyer *, même pas sur les endpoints que tu ranges mentalement dans « public ». Garde le wildcard pour ce qui est vraiment ouvert et sans état : un CDN de polices, un ticker de prix public, le genre d'API où tout ce qui compte est déjà dans l'URL.

Comment déployer des changements CORS sans casser le trafic

Sois plus méfiant avec les changements CORS qu'avec la plupart des ajustements de config. La vérif tourne dans le navigateur, donc un mauvais header ne fait rien planter bruyamment. Ça casse juste discrètement la tranche d'utilisateurs dont les navigateurs appliquent justement la règle exacte que tu as ratée. Envoie en staging et fais le même test navigateur que tu ferais en prod, parce que Safari devient bizarre aux marges d'une façon que Chrome et Firefox n'ont pas. Surveille ton compteur d'OPTIONS dans les access logs en direct pendant que ça se déploie : un pic soudain, ou une chute, pointe en général un souci de cache de preflight ou un handler endormi au volant. Et garde Access-Control-Max-Age bas pendant le déploiement, dans les 60 secondes, comme ça une régression, c'est un truc que tu peux retirer d'un coup au lieu d'attendre que le cache de chaque navigateur expire. Stable depuis un moment ? Remets-le à 86400 et arrête de fixer les logs.

Questions fréquentes

Pourquoi mon CORS marche dans Postman mais pas dans le navigateur ?

Parce que Postman n'est pas un navigateur, et le navigateur est la seule chose qui applique CORS. Un appel qui marche dans Postman ou curl te dit une chose : ton serveur veut bien y répondre. Sur le fait qu'un navigateur laissera ton JavaScript lire ce résultat ? Rien. Donc teste dans un vrai navigateur. Et jette Safari dans le lot si l'iOS te tient à cœur, vu que c'est lui qui te surprend.

Faut-il utiliser le wildcard pour une API publique ?

Seulement si elle est vraiment publique. Pas d'auth, pas d'état par appelant, et tu es absolument certain de ne jamais envoyer de credentials. À la seconde où il te faut un cookie ou un header Authorization depuis le navigateur, ce wildcard devient un mur et tu le réécris en allow-list de toute façon. Donc épargne-toi la migration future et démarre directement avec l'allow-list. C'est la même quantité de config dans les deux cas, honnêtement.

Quelle est la différence entre une requête simple et une non-simple ?

Une requête simple, c'est un GET, un HEAD, ou un POST dont le Content-Type est application/x-www-form-urlencoded, multipart/form-data ou text/plain, ne transportant rien d'autre que des headers standard. Franchis cette ligne où que ce soit (un PUT, un DELETE, un Content-Type application/json, un header Authorization) et te voilà non-simple, ce qui veut dire qu'un preflight part en premier. Le preflight, c'est le plus pinailleur des deux. Donc c'est en général exactement là que ça casse.

Je peux pas juste coller un proxy CORS devant mon API ?

Tu bricoles en local ? Bien sûr, vas-y. En production ? S'il te plaît, non. Un proxy t'achète de la latence et un tout nouveau point unique de défaillance, plus une frontière de sécurité de plus que tu dois désormais durcir et chouchouter à vie. Corrige juste le serveur d'API pour qu'il envoie les bons headers. Ce sont les mêmes cinq lignes de nginx que tu t'apprêtais à écrire de toute façon, sauf que cette version-là est vraiment à toi.

Mon CORS est nickel mais je vois « Failed to fetch » dans la console, je fais quoi ?

Ce « Failed to fetch » sec, sans le moindre détail, c'est le navigateur qui se montre inutile exprès. Neuf fois sur dix c'est l'une de deux choses. Soit le preflight a été rejeté d'emblée (ton serveur a balancé un 405 ou un 500 sur OPTIONS), soit la connexion n'a jamais abouti. Le DNS est mort. Du mixed content a été bloqué. Un firewall l'a avalé. Ouvre l'onglet Network et traque un OPTIONS rouge posté juste avant la requête qui a échoué. Cette petite ligne rouge, c'est presque toujours la bonne.