Échecs de pré-vérification CORS : déboguer les requêtes OPTIONS avec les outils de développement et cURL
L’URL d’une API s’ouvre lorsque vous la saisissez directement dans le navigateur, mais un appel fetch() à cette adresse échoue depuis votre application web. Ou le même point d’accès répond correctement dans Postman et cURL, tandis que le navigateur affiche une erreur CORS. Si une requête OPTIONS inattendue apparaît dans le panneau Réseau, il peut s’agir d’un échec de pré-vérification CORS, aussi appelée preflight.
Pour avancer, distinguez trois questions : le navigateur a-t-il envoyé une pré-vérification ? Le serveur a-t-il répondu avec les en-têtes nécessaires ? La requête réelle a-t-elle ensuite été envoyée ? Ce guide suit cette séquence avec un exemple reproductible.
CORS contrôle l’accès du navigateur, pas la connexion à l’API
Le partage de ressources entre origines, ou CORS, indique au navigateur si du JavaScript provenant d’une origine peut lire une réponse d’une autre origine. Une origine associe un schéma, un hôte et un port. https://app.example.com et https://api.example.com ont des origines différentes, même si leurs noms de domaine se ressemblent.
Ouvrir un point d’accès d’API par une navigation ordinaire n’est pas la même opération que lire une réponse entre origines depuis un script. De même, cURL peut récupérer une réponse d’API sans appliquer les règles CORS du navigateur. Dire que cela fonctionne dans cURL ne suffit donc pas : cela établit qu’un client HTTP a reçu une réponse, pas que le navigateur peut la rendre accessible à JavaScript.
CORS ne remplace pas non plus l’authentification ou l’autorisation. Un serveur peut légitimement renvoyer 401 Unauthorized ou 403 Forbidden après la réussite des contrôles CORS. Traitez ces erreurs applicatives séparément, au lieu d’assouplir les en-têtes CORS pour faire disparaître un message de la console.
Qu’est-ce qui déclenche une pré-vérification ?
Le navigateur demande parfois la permission avant d’envoyer une requête vers une autre origine. Il envoie une requête OPTIONS contenant l’origine demandeuse, la méthode prévue et, éventuellement, les noms des en-têtes envisagés.
Par exemple, cette requête JavaScript entre origines nécessite une permission préalable, car elle utilise PUT, définit un en-tête Authorization et envoie du JSON. Le navigateur envoie une requête OPTIONS, sauf si une permission correspondante figure déjà dans son cache de pré-vérification :
const response = await fetch("https://api.example.com/v1/items/42", {
method: "PUT",
headers: {
"Authorization": "Bearer example-test-token",
"Content-Type": "application/json"
},
body: JSON.stringify({ label: "Updated item" })
});
Ici, example-test-token est une valeur fictive de test, pas un véritable identifiant. Pour comparer JSON aux autres corps de requête, consultez JSON, formulaire encodé ou multipart : envoyer la bonne requête HTTP avec cURL.
Toutes les requêtes entre origines ne nécessitent pas de pré-vérification. Selon les règles Fetch/CORS, certaines combinaisons de méthodes, d’en-têtes et de types de contenu appartiennent à la liste des valeurs autorisées sans pré-vérification, dite CORS-safelisted, et peuvent être envoyées directement. Même dans ce cas, la réponse doit contenir les en-têtes CORS appropriés pour que JavaScript puisse la lire.
Étape 1 : repérer la requête OPTIONS dans les outils de développement
Ouvrez la page qui appelle l’API. Dans Chrome, Edge, Firefox ou Safari, ouvrez les outils de développement, sélectionnez Réseau, retirez les filtres restrictifs et vérifiez que l’enregistrement est actif. Déclenchez de nouveau l’action. Activez la conservation du journal si une navigation ou une redirection risque de vider la liste.
Recherchez une ligne OPTIONS juste avant la tentative de PUT ou de POST. Sélectionnez-la et examinez :
- L’URL de la requête : correspond-elle au point d’accès attendu, sans barre oblique finale accidentelle ni redirection ?
- Les en-têtes de requête : quelles sont les valeurs de
Origin,Access-Control-Request-MethodetAccess-Control-Request-Headers? - Le statut de la réponse : la réponse vient-elle d’une passerelle, d’un proxy, d’une couche d’authentification ou de l’application ?
- Les en-têtes de réponse : les valeurs autorisant l’origine, les méthodes et les en-têtes sont-elles présentes et compatibles ?
Selon le navigateur et l’erreur, la requête réelle à l’API peut ne jamais apparaître. C’est un indice utile : le navigateur peut s’arrêter après la pré-vérification si la permission n’est pas accordée.
Pour les raccourcis propres à chaque navigateur et une méthode de capture avec des données de test, consultez Comment vérifier les requêtes réseau d’un site dans Chrome, Firefox, Edge et Safari. Ne partagez pas de fichiers HAR sans les expurger : ils peuvent contenir des jetons, des cookies et des informations personnelles.
Étape 2 : lire un échange de pré-vérification réussi
Pour la requête PUT précédente, le navigateur pourrait envoyer l’équivalent de :
OPTIONS /v1/items/42 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization,content-type
Un service configuré pour accepter cette requête pourrait répondre :
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, PUT, OPTIONS
Access-Control-Allow-Headers: authorization, content-type
Vary: Origin
Une pré-vérification réussie nécessite un statut 2xx ; 204 est courant pour une réponse vide, mais le statut seul ne prouve pas la réussite. Le navigateur vérifie aussi les en-têtes de réponse. Le nom des en-têtes est insensible à la casse ; cet exemple illustre l’échange sans imposer une sérialisation exacte.
Access-Control-Allow-Origin doit désigner une origine demandeuse autorisée. Access-Control-Allow-Methods doit couvrir la méthode proposée, et Access-Control-Allow-Headers doit autoriser les en-têtes demandés qui ne figurent pas dans la liste CORS des en-têtes autorisés sans pré-vérification. Lorsque l’application choisit dynamiquement l’origine autorisée, Vary: Origin aide les caches partagés à ne pas réutiliser une réponse pour la mauvaise origine.
Une pré-vérification réussie ne prouve pas que le PUT suivant réussira. La réponse réelle doit elle aussi porter les en-têtes CORS nécessaires, et l’authentification, la validation, les règles métier et les limites de débit continuent de s’appliquer.
Étape 3 : reproduire la pré-vérification avec cURL
Reprenez l’URL, l’origine, la méthode et les noms d’en-têtes relevés dans les outils de développement. Pour un shell POSIX :
curl --include --request OPTIONS \
--url 'https://api.example.com/v1/items/42' \
--header 'Origin: https://app.example.com' \
--header 'Access-Control-Request-Method: PUT' \
--header 'Access-Control-Request-Headers: authorization,content-type'
Il s’agit d’une simulation de diagnostic. cURL n’applique pas automatiquement la logique de pré-vérification du navigateur et ne décide pas si le résultat est autorisé. Vous devez comparer les en-têtes de réponse aux règles du navigateur, puis refaire le test dans celui-ci.
Le générateur de commandes cURL gratuit permet d’assembler la méthode, l’URL et les en-têtes personnalisés avant de copier la commande dans votre terminal. Il prépare une commande ; il n’envoie pas la requête. Ajoutez --include pour afficher les en-têtes de réponse sur la sortie standard, ou activez l’option de sortie détaillée du générateur et examinez attentivement le résultat.
Si l’URL comporte des paramètres de requête, l’encodeur et décodeur d’URL peut vous aider à examiner les valeurs encodées et les clés répétées avant de coller une URL corrigée dans la commande.
Étape 4 : distinguer les échecs courants
| Observation | Piste à examiner | Vérification suivante |
|---|---|---|
OPTIONS renvoie 404 ou 405 | La route ou le serveur ne gère pas la méthode de pré-vérification | Vérifier la gestion d’OPTIONS dans la passerelle et le routeur |
OPTIONS renvoie 401 ou 403 | La couche d’authentification bloque le contrôle de permission | Examiner le traitement des pré-vérifications avant l’authentification |
Une redirection 301, 302 ou autre apparaît | La pré-vérification est redirigée par le passage à HTTPS ou la normalisation de la route | Tester directement l’URL HTTPS finale |
204, mais sans Access-Control-Allow-Origin | Le serveur a répondu sans autoriser l’accès entre origines | Vérifier la configuration CORS de cette route |
Les en-têtes autorisés omettent authorization | L’en-tête demandé n’est pas approuvé | Comparer Access-Control-Request-Headers à la liste autorisée |
| Le navigateur signale un échec, mais cURL réussit | cURL n’applique pas les règles CORS du navigateur | Vérifier la requête du navigateur et son mode d’envoi des identifiants |
| La pré-vérification réussit, mais la requête réelle échoue | La réponse applicative, l’authentification ou les en-têtes CORS de la réponse réelle posent problème | Examiner la requête suivante et sa réponse |
L’échec peut aussi provenir d’un proxy inverse, d’une passerelle d’API, d’un CDN ou du routage d’une plateforme serverless. Un middleware générique d’activation de CORS peut rester sans effet si une autre couche intercepte la méthode OPTIONS avant lui.
Étape 5 : traiter les cookies et les identifiants avec précision
Une tentative de correction fréquente consiste à renvoyer ces deux en-têtes ensemble :
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Cette combinaison n’autorise pas une réponse CORS avec identifiants dans les navigateurs. Pour une requête utilisant credentials: "include", renvoyez une origine explicitement autorisée et Access-Control-Allow-Credentials: true si cet accès est voulu, dans la réponse de pré-vérification comme dans la réponse réelle. Cette règle s’applique même si aucun cookie n’est disponible à l’envoi. L’exemple précédent avec jeton Bearer n’utilise pas credentials: "include" ; son en-tête Authorization explicite doit tout de même être autorisé par son nom. Selon la spécification, la pré-vérification elle-même ne transporte normalement ni cookies ni identifiants d’authentification HTTP, même si la requête réelle utilise des identifiants.
Un autre piège consiste à confondre inter-origines et intersites. Les règles SameSite et les politiques relatives aux cookies tiers peuvent empêcher l’envoi d’un cookie même si CORS est correctement configuré. Inversement, ajouter credentials: "include" à fetch() ne permet pas d’envoyer un cookie que ces règles bloquent. Comparez les en-têtes envoyés par le navigateur et le comportement de la session côté serveur. L’article localStorage, sessionStorage ou cookies : où stocker les données d’une application web ? fournit des explications complémentaires.
Ne recopiez jamais une valeur Origin arbitraire dans Access-Control-Allow-Origin pour faire taire les erreurs lorsque l’accès à des points d’accès privés doit être restreint. Définissez une liste explicite d’origines autorisées, cohérente avec la sécurité de votre API.
Étape 6 : vérifier les caches, les redirections et les environnements
Les navigateurs peuvent mémoriser une pré-vérification réussie selon Access-Control-Max-Age, dans les limites propres à chaque navigateur. Ce cache est distinct du cache HTTP ordinaire des réponses. Si les changements semblent produire des résultats incohérents, refaites le test dans un nouveau contexte de navigateur et comparez exactement les URL, méthodes et en-têtes, sans supposer qu’une simple actualisation vide tous les caches.
CORS peut se comporter différemment sur localhost, en préproduction et en production, car l’origine inclut le port et le schéma. http://localhost:3000 n’est pas http://localhost:5173, et https://app.example.com n’est pas http://app.example.com.
Vérifiez les redirections et les URL canoniques, surtout après une migration vers HTTPS ou l’ajout d’une redirection normalisant la barre oblique finale. Pour le cache des réponses, consultez Pourquoi votre site sert d’anciens fichiers : déboguer Cache-Control et les ETags avec cURL. Le cache des réponses et celui des pré-vérifications sont deux sujets de diagnostic liés, mais leurs mécanismes sont différents.
Une méthode de diagnostic CORS reproductible
Partez d’un échec dans le navigateur, plutôt que d’une commande cURL construite au hasard. Relevez l’origine, la méthode prévue, les en-têtes proposés et l’URL exacte. Examinez le statut et les en-têtes d’autorisation de la pré-vérification. Reproduisez cet échange OPTIONS depuis le terminal. Vérifiez la configuration du point d’accès et de la passerelle. Relancez ensuite l’action dans le navigateur et examinez sa réponse distincte, y compris les cookies et le comportement de l’authentification si nécessaire.
Le guide CORS de Mozilla explique les échanges et les erreurs courantes. Le standard Fetch (en anglais) définit l’algorithme de pré-vérification. L’objectif est d’autoriser les requêtes voulues sans élargir inutilement l’accès.
Pour préparer rapidement une reproduction partageable avec des données de test sans risque, ouvrez le générateur cURL de Web2Generators et comparez sa commande aux informations du panneau Réseau.