Gérer le CORS avec Node.js

Gérer le CORS avec Node.js

sécurité, nodejsPublié il y a 9 mois

Sommaire

Non nous n'allons pas parler ici de l'île de beauté, mais du CORS pour Cross-Origin Resource Sharing ou Partage des Ressources entre Origines Multiples. Si vous ne connaissez pas, ce système permet de ce protéger des appels XHR depuis une origine différente.

Imaginons que nous possédons un service sous forme d'API consommable. Par exemple http://api.exemple.com. Maintenant imaginons que nous possédons également une application web qui va consommer ce service. Par exemple http://app.exemple.com. Si on souhaite charger des données de http://api.exemple.com sur notre application http://app.exemple.com notre navigateur va faire une requête dite d'inter-origine et c'est là que le CORS rentre en jeu.

En effet, puisque la requête survient sur un domaine différent de http://api.exemple.com, notre navigateur va vérifier que la réponse du serveur autorise ce domaine à consulter la ressource.

Pour ce faire, le serveur peut envoyer des entêtes HTTP propre à CORS qui sont pour les principaux :

  • Access-Control-Allow-Origin : Permet de définir les origines autorisées à accéder à la ressource.
  • Access-Control-Allow-Methods : Permet de définir les méthodes autorisées pour les origines (GET, POST, DELETE etc …), à renseigner en réponse d'un pré-contrôle.
  • Access-Control-Allow-Headers : Permet de définir les entêtes autorisés pour les origines (Authorization etc …), à renseigner en réponse d'un pré-contrôle.

Inversement, la requête doit comporter quelques entêtes permettant de s'identifier auprès du serveur :

  • Origin : Le domaine d'origine voulant accéder à la ressource
  • Access-Control-Request-Method : La méthode employée, envoyée dans le cas d'un pré-contrôle
  • Access-Control-Request-Headers : Les entêtes utilisés, envoyés dans le cas d'un pré-contrôle

Ces entêtes sont gérés par le navigateur et il est théoriquement impossible de les surcharger.

Sachez enfin que pour des requêtes autre que GET ou POST, le navigateur va effectuer une première requête de pré-contrôle en appelant la ressource via la méthode OPTIONS. Il n’effectua la requête voulue que si le serveur lui retourne au minima l'entête Access-Control-Allow-Origin avec la bonne origine (dans notre exemple http://app.exemple.com) ou avec la valeur * signifiant que toutes les origines sont autorisées. Il peut également valider la méthode utilisée avec Access-Control-Allow-Methods, ainsi que les entêtes avec Access-Control-Allow-Headers.

Bien, maintenant que nous avons vu la théorie, passons à la pratique 😁 Si vous avez suivi, vous avez compris que votre API doit fournir au moins l'entête Access-Control-Allow-Origin en réponse pour pouvoir être consommée par un domaine différent.

Ainsi avant toute réponse, il est bon d'ajouter ces quelques lignes :

// index.js

const http = require('http');
const httpServer = http.createServer();

httpServer.on('request', (request, response) => {
    // On spécifie l'entête pour le CORS
    response.setHeader('Access-Control-Allow-Origin', '*');

    // On gère le cas où le navigateur fait un pré-contrôle avec OPTIONS ...
    // ... pas besoin d'aller plus loin dans le traitement, on renvoie la réponse
    if (request.method === 'OPTIONS') {
        // On liste des méthodes et les entêtes valides
        response.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Origin, Authorization');
        response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');

        return response.end();
    }

    // suite du traitement ...
});

httpServer.listen(8080);

ℹ️ Le bout de code est en Node.js, mais la logique est la même pour d'autres langages.

Nous venons d’autoriser n'importe quel domaine (avec *) à accéder aux URL de notre API, de pouvoir utiliser les entêtes Content-Type, Accept, Origin, Authorization ainsi que les méthodes GET, POST, PUT, DELETE, PATCH, OPTIONS, bref c'est open bar 🍾

C'est bien pour tester son API ou si on souhaite que notre API soit totalement public, mais c'est rarement le cas et niveau sécurité ce n'est pas l'idéale. Ajoutons un peu de contrôle.

// index.js

const http = require('http');
const httpServer = http.createServer();

// Mes origines acceptées
const allowOrigins = ['http://app.exemple.com', 'http://autre.exemple.com'];

httpServer.on('request', (request, response) => {

    // On test si l'entête "Origin" fait partie des origines acceptées
    if (request.headers['origin'] && allowOrigins.includes(request.headers['origin'])) {

        // Si oui alors on renseigne "Access-Control-Allow-Origin" avec l'origine de la requête
        response.setHeader('Access-Control-Allow-Origin', request.headers['origin']);
    } else {

        // Sinon on renseigne "Access-Control-Allow-Origin" à null créant une erreur CORS dans le navigateur
        response.setHeader('Access-Control-Allow-Origin', 'null');
    }

    if (request.method === 'OPTIONS') {
        response.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Origin, Authorization');
        response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');

        return response.end();
    }

    // suite du traitement ...
});

httpServer.listen(8080);

Ainsi on vérifie que l'origine de la requête est acceptée par le serveur, c'est très important pour se prémunir des attaques XHR ou bien, plus vicieux, des attaques CSRF 🕵️

D'ailleurs je vous recommande une très bonne conférence du Mixit 2019 de Julien Topçu sur le sujet :

Comment se faire hacker bien comme il faut

⚠️ Attention cependant, il y a toujours moyen de surcharger les entêtes HTTP et de fournir une origine acceptée par le serveur, je vous conseille de doubler votre sécurité avec une vérification de l'IP par exemple. De plus CORS ne remplace pas un système d’authentification ni d’autorisation il faut le voir comme une première couche de sécurité.

Commentaire(s) 

Ancun commentaire pour le moment.