Parlons CORS mais parlons bien

Voyons pourquoi la notion de CORS est importante.

Une erreur bien connue

On découvre assez souvent la notion à l’occasion d’une erreur AJAX dans la console du navigateur:

Cross-Origin Request Blocked

ou encore en essayant de charger une ressource depuis une URL distante:

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

On trouve pas mal de threads et autres stackoverflows qui indiquent d’ajouter des écritures dans le .htaccess du site pour corriger l’erreur:

Access-Control-Allow-Origin: *

Il peut y avoir des variantes bien sûr mais la plupart du temps c’est ce genre de solutions qui ressort.

Qu’est-ce que le CORS?

CORS signifie Cross-Origin Resource Sharing. Le principe est de pouvoir partager des ressources hébergées sur le serveur avec des sous-domaines ou d’autres domaines.

Si mon site est hébergé sur le serveur ALPHA, il ne pourra intéragir qu’avec des ressources qui elles aussi sont hébergées sur ALPHA à moins de paramétrer le CORS.

Cette politique de sécurité s’appelle le “same-origin” (SOP), elle est appliquée par les navigateurs web pour mitiger les risques d’injections malicieuses ou de renifleries.

Vous imaginez l’enfer sinon? Il faudrait se déconnecter de tout service ou application avant de naviguer ailleurs car n’importe quel site vérolé ou mal intentionné (ce qui revient au même au final) pourrait très facilement aspirer les données issues de la session en cours.

Bien comprendre la notion d’origine

L’origine en question ne concerne pas seulement le domaine mais aussi le port et le protocole. Il faut vraiment que les trois soient les mêmes pour que le navigateur considère des ressources comme étant “same origin”.

Les versions http et https du site ne sont pas considérées comme étant de la même origine par le navigateur.

L’objectif n’est pas seulement de contrôler qui d’Henry ou Martine pourra accéder mais aussi de quelle manière. Ainsi on pourra autoriser certaines requêtes et pas d’autres.

Il est d’ailleurs assez standard que seules les requêtes GET (en lecture) soient autorisées et pas les requêtes PUT, PATCH et DELETE (écriture). Même si Henry et Martine ont le droit d’accéder, on ne va quand même pas leur permettre de venir supprimer ou mettre à jour des éléments.

Notions d’en-têtes et de corps

Dans la très grande majorité des cas on parle de requêtes HTTP avec le CORS. Dans une requête HTTP, on a ce qu’on appelle des HTTP Headers, en français “en-têtes”.

Grâce à ces en-têtes, le navigateur discute en quelque sorte avec le serveur. Les en-têtes servent à décrire le contenu des requêtes envoyées et reçues.

Lorsqu’on paramètre le CORS, on ajoute un nouvel en-tête qui permet de spécifier comment la ressource est partagée.

Le body, en français “corps”, est l’autre partie de la requête, celle qui contient les données. Cela peut être par exemple une image, du code HTML, du texte, des données au format JSON, etc.

Le dialogue client/serveur

Je le disais plus haut, le navigateur et le serveur dialoguent via les en-têtes. Quand une requête cross-origin est faite, le navigateur va ajouter un en-tête Origin décrivant le protocole (https), le domaine et le port.

Le serveur reçoit cet en-tête mais pour que l’accès soit validé il devra renvoyer un en-tête Access-Control-Allow-Origin en spécifiant l’origine.

Mettre un * signifie que la ressource sera partagée avec absolument tout le World Wide Web. De manière alternative, on peut préciser un ou plusieurs domaines autorisés à la place.

Quand le navigateur reçoit cet en-tête de la part du serveur, il regarde si la règle est compatible et autorise ou pas l’accès.

Requêtes preflight

On vient de voir un cas de requête simple mais lorsque la requête est plus complexe, avec des critères moins “standards”, une pré-requête de vérification est lancée par le navigateur avant la requête principale.

Ce que j’appelle “critères moins standard” ce sont par exemple les Content-Type application/json ou des requêtes du type PUT, PATCH, DELETE et pas GET, POST, HEAD.

Si le serveur répond au client (le navigateur) qu’il ne supporte pas tout ce qui est demandé la requête principale sera bloquée.

Ces requêtes preflight sont des requêtes de type OPTIONS.

Identifiants et CORS

Par souci du respect de la vie privée, le CORS concerne, par défaut de paramétrage, des requêtes anonymes, sans élément qui puisse identifier le requêteur comme par exemple un cookie.

C’est pour ça que l’on doit rajouter un credentials: 'include' à nos requêtes côté client si on a besoin d’avoir du CORS authentifié:

fetch('https://lesite.fr', {
  mode: 'cors',
  credentials: 'include'
})

Côté serveur, un en-tête supplémentaire Access-Control-Allow-Credentials doit être présent et paramétré à true et il est logiquement impossible d’utiliser une wilcard pour l’entête Access-Control-Allow-Origin. Il faut préciser les origines autorisées.

Bonnes pratiques et réflexions

La wildcard * doit être évitée si possible, soyez plus sélectifs et restrictifs dès que vous le pouvez en matière de sécurité de toute façon.

Avoir une liste blanche (ou une liste noire) est une bonne pratique et si vous avez besoin de partager des cookies et autres éléments sensibles, vous en aurez besoin.

Attention avec le concept de CORS. Il ne s’agit pas d’un mécanisme de sécurité, c’est même le contraire. On lâche du leste et c’est pour cela qu’il faut être prudent dans le paramétrage.

Néanmoins, le CORS, s’il est bien paramétré permet de partager des ressources entre plusieurs domaines, ou entre un domaine et ses sous-domaines, ce qui s’avère nécessaire dans de nombreux cas.

Comment limiter les risques

Je le suggérais dans la section précédente: le CORS n’est pas une sécurisation, il peut être contourné.

De manière générale, mais aussi dans le cas qui nous intéresse, faire des listes blanches et noires est une bonne chose. Cela vous donnera en plus une vision d’ensemble.

Ensuite, listez vos ressources. Avez-vous vraiment besoin d’autoriser l’accès à toutes? Il y a probablement différents réglages CORS à effectuer selon les ressources par ailleurs.

Suivant la nature des données, par exemple pour une donnée confidentielle qui authentifie un utilisateur, on n’appliquera pas les mêmes restrictions.

Toutes les requêtes en écriture (POST, PUT, PATCH, etc) sont nécessairement plus sensibles.

Enfin, s’il s’agit d’ouvrir un accès pour un de vos partenaires ou un service tiers, il est très probablement mieux indiqué de passer par une authentification type oAUTH2 que d’appliquer mécaniquement des règles CORS.

Webographie