Comprendre CORS et les accès entre origines

Auteur : Publié le

En bref : CORS (Cross-Origin Resource Sharing) est un mécanisme qui permet à un serveur d’autoriser le navigateur à transmettre une réponse à un script exécuté depuis une autre origine. Les en-têtes de réponse du serveur définissent cette autorisation, et le navigateur veille à son respect. Une erreur CORS signale donc généralement un problème d’autorisation de lecture de la réponse, pas nécessairement une absence de connexion au serveur.

Pourquoi le navigateur limite les accès entre origines

Les navigateurs appliquent la politique de même origine, ou same-origin policy. Celle-ci limite la possibilité, pour un script, de lire des données provenant d’une autre origine. Ainsi, une page ouverte dans un onglet ne peut pas récupérer et lire librement des données privées sur d’autres services auxquels l’utilisateur est connecté.

Une origine (origin) réunit trois éléments : le protocole, le nom d’hôte et le port. Il suffit qu’un seul de ces éléments change pour que l’origine soit différente. Le chemin qui suit le nom d’hôte n’entre pas en compte.

Pour la page https://boutique.example, voici la comparaison :

Adresse cibleMême origine ?Raison
https://boutique.example/produitsOuiSeul le chemin change
https://api.boutique.exampleNonLe nom d’hôte est différent
http://boutique.exampleNonLe protocole est différent
https://sklep.example:8443NonLe port diffère du port par défaut, 443

CORS permet au serveur d’assouplir cette règle de manière contrôlée. Ce n’est pas une méthode HTTP, comme GET ou POST, mais un mécanisme fondé sur les en-têtes HTTP.

Exemple simple : une boutique récupère une liste de produits

Supposons que le site soit accessible à l’adresse https://boutique.example et que l’interface donnant accès aux données de l’application, son API, se trouve à l’adresse https://api.boutique.example. Le script de la page veut récupérer les produits :

fetch('https://api.sklep.example/produkty')
  .then(reponse => reponse.json())
  .then(produits => console.log(produits));

Comme les adresses ont des origines différentes, le navigateur ajoute à la requête un en-tête indiquant l’origine de la page :

Origin: https://sklep.example

Le serveur de l’API peut renvoyer les données accompagnées d’un en-tête d’autorisation :

Access-Control-Allow-Origin: https://sklep.example

Le navigateur compare cette valeur à l’origine de la page. Si l’autorisation correspond, il transmet la réponse au script. Si l’en-tête est absent ou indique une autre adresse, le script ne peut pas lire la réponse, même si le serveur a renvoyé le statut 200 OK.

L’en-tête d’autorisation doit provenir du serveur de l’API. Ajouter Access-Control-Allow-Origin dans le code de la requête côté navigateur ne résout pas le problème.

Quand le navigateur envoie d’abord OPTIONS

Les requêtes entre origines différentes ne sont pas toutes envoyées immédiatement. Pour certaines, le navigateur effectue d’abord un preflight, c’est-à-dire une requête préliminaire qui vérifie l’autorisation du serveur.

Par exemple, l’envoi d’une commande avec la méthode POST et le type de contenu application/json déclenche cette étape. L’utilisation de l’en-tête Authorization la déclenche également. La requête préliminaire peut se présenter ainsi :

OPTIONS /commandes HTTP/1.1
Origin: https://sklep.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

Le serveur doit répondre par une autorisation couvrant l’origine, la méthode et les en-têtes nécessaires :

Access-Control-Allow-Origin: https://sklep.example
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type

Le navigateur n’envoie la véritable requête POST qu’une fois cette vérification réussie. La réponse à cette requête POST doit elle aussi contenir l’en-tête Access-Control-Allow-Origin approprié.

Toutes les requêtes POST ne nécessitent pas de preflight. Les en-têtes et le type de contenu envoyé comptent également ; une simple requête GET sans en-têtes supplémentaires n’a souvent pas besoin de requête préliminaire.

Cookies et astérisque : deux pièges importants

Une API publique dont les réponses doivent être accessibles aux scripts de n’importe quelle origine peut renvoyer :

Access-Control-Allow-Origin: *

L’astérisque ne convient toutefois pas aux requêtes envoyées avec des cookies dans un mode incluant les informations d’authentification. Dans ce cas, le code peut utiliser credentials: 'include', et le serveur doit indiquer une origine autorisée précise et renvoyer :

Access-Control-Allow-Origin: https://sklep.example
Access-Control-Allow-Credentials: true

La configuration CORS ne garantit pas à elle seule l’envoi des cookies. Les règles relatives aux cookies, notamment SameSite, ainsi que les restrictions du navigateur en matière de confidentialité s’appliquent aussi.

Si le serveur accepte plusieurs origines de confiance, il doit vérifier la valeur de l’en-tête Origin à l’aide d’une liste de valeurs autorisées. Renvoyer sans vérification n’importe quelle origine reçue en autorisant les informations d’authentification peut exposer des réponses privées. Lorsque la réponse dépend de Origin, il faut également prévoir Vary: Origin pour que le cache distingue les différentes variantes de la réponse.

Comment diagnostiquer une erreur CORS

Dans les outils de développement du navigateur, ouvrez l’onglet Réseau et effectuez quatre vérifications :

  1. Comparez le protocole, l’hôte et le port de la page avec ceux de l’API.
  2. Vérifiez si une requête OPTIONS est envoyée et si le serveur y répond correctement.
  3. Examinez les en-têtes de réponse de la requête réelle, y compris en cas d’erreur du serveur.
  4. Vérifiez que l’autorisation couvre la méthode, les en-têtes et les éventuelles informations d’authentification utilisés.

Le fait qu’un appel fonctionne avec curl ne prouve pas que CORS est correctement configuré : cet outil n’applique pas les restrictions du navigateur. Le réglage mode: 'no-cors' ne débloque pas non plus la lecture du JSON : il produit une réponse opaque, dont le contenu n’est pas accessible au script.

CORS ne remplace ni la connexion à un compte, ni le contrôle des autorisations, ni la protection contre les attaques CSRF, qui consistent à faire déclencher des actions non souhaitées par un autre site. Certaines requêtes peuvent atteindre le serveur même si la lecture de leur réponse est bloquée. Pour corriger le problème, commencez par la configuration de l’API ou d’un intermédiaire que vous contrôlez, plutôt que de désactiver les protections du navigateur.

À lire aussi : Qu’est-ce qu’une API ?

À lire aussi

Découvrez comment PHP-FPM traite les requêtes PHP, son rôle face au serveur web et comment adapter ses processus au trafic.
Découvrez BIND, logiciel libre de serveur DNS : ses rôles, la gestion des domaines et les cas où le déployer vous-même.
Découvrez le registre polonais REGON, son numéro, ses différences avec NIP et KRS et comment vérifier les données d’une entreprise.