Node.js - Du Javascript coté serveur

Auteur: Mohamed CHINY Durée necessaire pour le cours de Node.js - Du Javascript coté serveur Niveau recommandé pour le cours de Node.js - Du Javascript coté serveur Supports vidéo disponibles pour ce cours Exercices de renforcement non disponibles pour ce cours Quiz non disponibles pour ce cours

Page 12: CORS - Cross-Origin Resource Sharing

Toutes les pages

Cross-Origin Resource Sharing - CORS

Cross Origin (ou origine multiple) c'est quoi?

Quand on développe une application Web, les requêtes qu'on lui passe proviennent de l'application elle même. C'est à dire que l'application entière est hébergée au même endroit (même domaine et même port d'écoute). D'ailleurs, quand vous spécifiez l'attribut src des images, vous mettez souvent un chemin relatif qui sous-entend que la page qui demande l'image est présente au même endroit que l'image elle même (mis à part le fait de traverser quelques dossiers pour accéder à la bonne ressource). C'est aussi le cas des appels de fichiers CSS, scripts Javascript, fichiers de police... On dit alors qu'on est sur le même domaine. Donc, on estime que les programmes appelants sont dignes de confiance car ils font partie de l'application.

Par ailleurs, ll se peut que les ressources demandées soient hébergées dans un serveur différent, comme c'est les cas des CDN (Content Delevery Network) qui hébergent souvent les images et d'autres ressource pour accélérer leur chargement sur le navigateur. Dans ce cas précis, le programme appelant et la ressource appelée ne sont pas dans le même domaine. On parle alors de domaine multiple ou cross origin.

Il faut savoir que les navigateurs utilisent ce qu'on appelle same-origin policy (ou politique de même origine). Donc, une page Web ne peut pas (par défaut) accéder aux ressources d'autres origines.
Il est important de savoir que l'appel des images, le CSS et les polices (et parfois même les scripts) ne subit pas les restrictions appliquées par le CORS, car ces appels-là ne présentent (souvent) pas de danger. Donc, les détails expliquées plus haut servent juste à simplifier l'idée du cross origin.

Appels asynchrones via XMLHttpRequest ou l'API Fetch

Dans les cours consacrés à l'objet XMLHttpRequest d'AJAX et l'API Fetch, nous avons vu qu'il est possible d'interroger le serveur via les requêtes HTTP qui implémentent le CRUD (GET, POST, PUT et DELETE) via des programmes Javascript dans le mode asynchrone (ou non bloquant). Dans ce cas précis, si le script appelant n'est pas hébergé dans le même serveur, alors les restrictions du CORS sont appliquées, afin d'accroître la sécurité et interdire les requêtes malveillantes.

Quand on développe une API avec Node.js (ou Express.js), on se contente de créer la partie backend de l'application Web. Pour interroger le backend, on a besoin de développer des scripts appelants et qui font partie du frontend. Ces scripts là implémentent souvent l'objet XMLHttpRequest ou l'API Fetch. Cependant, le programme appelant est forcément hébergé dans un domaine différent, ou au moins, utilise un port d'écoute différent.
On parle d'origines multiples quand les deux origines sont des domaines différents ou utilisent des ports d'écoute différents.

Qu'est ce qu'il faut spécifier pour le CORS?

Quand une application A appelle les ressources de l'application B, alors elle envoie un entête de type origin. L'application B répond à la requête en spécifiant généralement trois points:
  • Access-Control-Allow-Origin: qui indique les domaines autorisées à interroger la ressource.
  • Access-Control-Allow-Methods: pour spécifier la liste des méthodes HTTP autorisées lors de l'appel.
  • Access-Control-Allow-Headers: qui sert à spécifier le entêtes personnalisées autorisées lors de l'appel.

L’entête Access-Control-Allow-Headers est utilisé pour les requêtes preflight CORS (requêtes de type OPTIONS). Il s'agit d'une requête préliminaire envoyées au serveur afin que celui-ci réponde en spécifiant les requêtes réellement autorisées pour l'échange de données (comme POST, PUT...)

Spécifier les règles du CORS dans Express.js

Bien que les entêtes soient normalisés, donc utilisent les mêmes spécifications techniques quelque soit le langage de programmation, leur implémentation quant-à-elle change d'un langage à un autre.

Dans le cas d'Express.js (ou Node.js en général), il faut d'abord installer le module cors comme ceci:
npm install cors
Ensuite, on enregistre un middleware dans la chaîne de traitement des requêtes HTTP où l'on spécifie le module CORS comme ceci:
const express = require('express')
const cors = require('cors')

const app = express()

app.use(cors())

app.get('/api', (req, res) => {
   res.json({
      status: 'Toutes les origines sont permises'
   })
})

app.listen(3000, () => {
   console.log('Serveur en écoute...')
})
Dans ce cas, toutes les origines sont autorisées, c'est à dire que quelque soit le domaine ou le port qui appelle l'application Express et quelque soit la méthode utilisée, la requête sera autorisée.

Si on veut appliquer des restrictions sur les CORS alors on spécifie les entêtes précédemment expliqués avec les valeurs voulues à l'aide d'un objet Javascript avant d'appliquer le middleware comme ceci:
const corsParams = {
   origin: 'https://chiny.me',
   methods: ['GET', 'POST', 'PUT'],
   allowedHeaders: ['Content-Type', 'Authorization'],
   credentials: true
};

app.use(cors(corsParams));
Dans ce cas, seule les requête provenant du domaine https://chiny.me et utilisant les méthodes GET, POST et PUT sont autorisées. Quant à la clé allowedHeaders qui implémente l'entête Access-Control-Allow-Headers indique dans ce cas que les entêtes de type Content-Type (qui spécifie le type de contenu envoyé par le serveur) et Authorization (qui sert à transmettre les jetons d'authentification comme JWT) sont autorisées.

L’option credentials: true indique que le programme appelant souhaite inclure des identifiants (comme les cookies ou les entêtes Authorization) dans les requêtes.
JWT (JSON Web Token) est un standard qui permet de transmettre les jetons (tokens) de sécurité entre deux parties. Il sert souvent à l'authentification et l'autorisation des transactions. Il s'agit d'un mécanisme statless qui n'a pas besoin de stoker les informations d'autorisation sur le serveur (comme les sessions), mais chaque requête envoie un token JWT indépendamment des autres requêtes.
On peut également appliquer les CORS pour une seule route. Donc au lieu de spécifier le middleware app.use(cors), qui sera applicable à toutes les routes, on fait comme ceci:
app.get('/api', cors(), (req, res) => {
   res.json({
      status: 'Toutes les origines sont permises'
   })
})
Dans ce cas, les CORS sont appliquées seulement à la route utilisant la méthode GET et le chemin /api.