Sécuriser une API REST (Partie 1) : Théorie

Temps de lecture : 8 min

Les API REST sont aujourd’hui omniprésentes, elles permettent la communication et l’échange de données entre applications et systèmes hétérogènes. Vient alors la question de la sécurisation de l’échange de ces données. Nous allons donc voir dans cet article comment sécuriser une API REST.

Qu’est-ce qu’une API REST

Avant de commencer, faisons un bref rappel sur ce qu’est une API REST. REST (pour REpresentational State Transfer) est un style d’architecture basé sur le protocole HTTP et qui permet de manipuler des ressources via un URI. Pour manipuler ces ressources, une API REST utilise les méthodes HTTP suivantes :

  • GET : Récupération d’une ressource;
  • POST : Ajout d’une ressource;
  • PUT : Mise à jour complète d’une ressource;
  • PUT : Mise à jour partielle d’une ressource;
  • DELETE : Suppression d’une ressource;
  • HEAD : Similaire à GET, mais permet uniquement de récupérer les en-têtes HTTP.

Par exemple une API REST qui gère des articles d’un blog pourrait mettre à disposition les actions suivantes :

ActionMéthodeURI
Récupérer la liste des articlesGET/api/posts
Récupérer un article en particulierGET/api/posts/8
Récupérer le détail d'un articleGET/api/posts/8/details
Ajouter un articlePOST/api/posts
Modifier un article en particulierPUT/api/posts/8
Supprimer un article en particulierDELETE/api/posts/8

Une API REST doit également respecter un certain nombre de contraintes :

  • Client-Serveur : Il y a séparation des rôles entre le client et le serveur permettant à chacun d’évoluer séparément;
  • Sans état (stateless) : Chaque requête du client contient toutes les informations nécessaires au traitement. Il n’y a donc pas de session côté serveur;
  • Cache : La réponse du serveur peut-être mise en cache côté client ou côté serveur;
  • Interface uniforme (Uniform Interface) : Chaque ressource doit être identifiable de manière unique via son URI, représentable (via le format XML ou JSON par exemple), manipulable via sa représentation (en utilisant les méthodes HTTP), autodescriptive (doit contenir toutes les informations pour son traitement, par exemple on peut connaitre son format via l’utilisation des types MIME);
  • Organisation en couches (Layered System) : Le système peut être séparé en plusieurs couches (serveurs proxy, load balancers, firewalls, etc.);
  • Code à la demande (Code-On-Demand) : Cette contrainte optionnelle permet au client d’exécuter du code à la demande, c’est-à-dire d’étendre une partie de la logique du serveur au client, via l’envoi de code JavaScript par exemple.

Sécurité

Maintenant que l’on sait ce qu’est une API REST, voyons comment sécuriser les échanges entre le client et celle-ci.

Utilisation de HTTPS

Bon, j’espère ne rien vous apprendre, mais la première étape de la sécurisation d’une API REST est l’utilisation du protocole HTTPS. Cela permettra de chiffrer les données transmises et reçues empêchant ainsi leur lecture.

Authentification

Il faut ensuite un mécanisme authentification des échanges entre le client et l’API REST. Comme je l’ai rappelé précédemment, une API REST est sans état (stateless) c’est-à-dire qu’il n’y a pas de session côté serveur pour l’authentification de l’utilisateur. De ce fait, chaque requête doit contenir les informations nécessaires à l’authentification.

On pourrait très bien utiliser la méthode “Basic” de l’authentification HTTP (“basic auth”), mais cela implique de transmettre pour chaque requête, le couple login/mot de passe ce qui n’est franchement pas top. Nous allons plutôt utilise un JSON Web Token (JWT).

Présentation de JWT

JWT ou JSON Web Token est un standard ouvert décrit dans la RFC 7519 qui permet l’authentification d’un utilisateur à l’aide d’un jeton (token) signé. Le principe est le suivant :

  • Lors du premier échange, le client envoie son couple login/mot de passe au serveur;
  • Si le couple est valide, le serveur génère un token et l’envoie au client. Ce token permettra d’authentifier l’utilisateur lors des prochains échanges;
  • Le client stocke ensuite le token en local;
  • Le token est renvoyé, par le client, pour chaque appel à l’API (via l’en-tête HTTP « Authorization ») permettant ainsi d’authentifier l’utilisateur.
Utilisation du JWT lors des échanges client-serveur
Utilisation du JWT lors des échanges client-serveur

Le token généré est composé de trois parties séparées par un point :

  • Header;
  • Payload;
  • Signature.

Pour la suite, prenons le JWT suivant :

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2NvZGVoZXJvZXMuZnIiLCJpYXQiOjE1MjEzMDk2MDAsImV4cCI6MTUyMTMxMzIwMCwiYXVkIjoiaHR0cHM6Ly9zaXRlY2xpZW50LmZyIiwic3ViIjoiMTI0Iiwicm9sZSI6InVzZXIifQ.Lml5MSnQKGhTxTtkM92sAEXxQEDvOYPtVZWphciwOiM

Header

La première partie du JWT est le header. Il s’agit d’un objet JSON encodé en base64 qui représente l’en-tête du token. Il est composé de deux parties :

  • Le type du token;
  • L’algorithme utilisé pour la signature.

Dans notre exemple de JWT, le header est “eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. Si nous le décodons, nous obtenons :

Payload

La seconde partie du JWT est le payload. Il s’agit tout comme le header, d’un objet JSON encodé en base64 qui représente cette fois-ci le corps du token. C’est dans cette partie que l’on mettra les informations de l’utilisateur (identifiant, rôle, etc.) ou toute autre information utile au serveur. Le standard définit trois types de propriétés (appelées claims) :

  • Propriétés réservées : Il s’agit de noms réservés définis par la spécification. On y retrouve notamment :
    • iss” (Issuer) : Permet d’identifier le serveur ou le système qui a émis le token;
    • sub” (Subject) :  Il s’agit généralement de l’identifiant de l’utilisateur que le token représente;
    • aud” (Audience) : Il s’agit généralement de l’application ou du site qui reçoit le token;
    • iat”  (Issued At) : Il s’agit de la date de génération du token;
    • exp” (Expiration Time) : Il s’agit de la date d’expiration du token.
  • Propriétés publiques : Il s’agit de noms normalisés tels que “email”, “name”, “locale”, etc. La liste complète est disponible à cette adresse;
  • Propriétés privées : Il s’agit de propriétés que vous définissez vous même pour répondre aux besoins de votre application.

Dans notre exemple de JWT, le payload est “eyJpc3MiOiJodHRwczovL2NvZGVoZXJvZXMuZnIiLCJpYXQiOjE1MjEzMDk2MDAsImV4cCI6MTUyMTMxMzIwMCwiYXVkIjoiaHR0cHM6Ly9zaXRlY2xpZW50LmZyIiwic3ViIjoiMTI0Iiwicm9sZSI6InVzZXIifQ“. Si nous le décodons nous obtenons :

Attention le payload ne doit pas contenir de données sensibles.

Signature

La dernière partie est la signature du token. Il s’agit d’un hash des deux premières parties du token réalisé en utilisant l’algorithme qui est précisé dans le header. Dans notre exemple de token ci-dessus, l’algorithme utilisé est HS256 (HMAC-SHA-256), la signature est donc créée de cette manière :

L’algorithme utilise une clé secrète (détenue par le serveur), utilisée pour signer les tokens mais également s’assurer de la validité de ceux-ci en vérifiant leur signature. De ce fait, si un utilisateur malveillant modifie le contenu du token, la signature ne sera plus correcte et le jeton sera ainsi rejeté. Dans notre exemple ci-dessus, si l’utilisateur change son rôle en “admin” la signature est bien modifiée :

  • Signature du token avec le rôle “user” : Lml5MSnQKGhTxTtkM92sAEXxQEDvOYPtVZWphciwOiM
  • Signature du token avec le rôle “admin” : bOSIz8-3jCRQJI4MrSh86T0EVNS_ZeY6cphSaGybZ1Y

Il est également possible d’utiliser d’autres types d’algorithmes pour la création de la signature (comme RSA-SHA256 par exemple).

Je vous invite à aller sur le site https://jwt.io/ pour tester vos JWT.

Expiration d’un token

Comme nous l’avons vu plus haut, un JWT possède une date d’expiration (propriété “exp” du payload), ainsi lorsqu’un JWT expiré est envoyé lors d’une requête à l’API, celle-ci renverra une erreur 401 indiquant que le JWT n’est plus valide. La durée de validité d’un JWT va dépendre du type de données échangées. Elle sera de quelques minutes pour l’échange de données sensibles à plusieurs heures pour les données non sensibles. Libre à vous de choisir cette durée de validité.

Afin d’éviter à l’utilisateur de devoir se réauthentifier lorsque le JWT expire, il est possible d’utiliser un “refresh token”. Lorsque l’utilisateur s’identifie à l’aide de son couple login/mot de passe, le serveur génère en plus du JWT, un token aléatoire à usage unique attaché à l’utilisateur (généralement sauvegardé en base de données).

Utilisation du "refresh token"
Utilisation du “refresh token”

Lorsque le JWT est expiré :

  • Le client (application ou site) envoie une requête sur une route particulière de notre API avec le JWT expiré et le “refresh token”;
  • Le serveur vérifie que le JWT est expiré, que le “refresh token” est valide et que celui-ci est bien associé à l’utilisateur du JWT expiré;
  • Le serveur génère un nouveau JWT, un nouveau “refresh token” (lié à l’utilisateur) et invalide l’ancien “refresh token” (par exemple suppression de la base de données ou un champ indiquant que ce token n’est plus valide);
  • Le serveur renvoie le nouveau JWT et “refresh token” au client;
  • Le client sauvegarde le JWT et le “refresh token”;
  • Le client peut utiliser le nouveau JWT.

Il est également possible d’ajouter une date d’expiration au “refresh token”.

Utilisation de clés API

L’utilisation des JWT est utile lorsque vous souhaitez authentifier un utilisateur, mais lorsque les consommateurs de votre API sont des applications ou des sites, il convient d’utiliser des clés d’API. Généralement, vous devrez fournir à l’application utilisant votre API :

  • Une clé d’API permettant d’identifier l’application;
  • Une clé secrète, partagée entre l’application et l’API REST, permettant de signer les requêtes et d’authentifier l’application.

Pour chaque requête :

  • L’application crée une signature de la requête à l’aide d’un algorithme HMAC et de la clé secrète. La signature est faite sur les en-têtes HTTP de la requête que vous aurez spécifiés (la méthode de construction de la signature est décrite ici : https://tools.ietf.org/html/draft-cavage-http-signatures-09);
  • L’application envoie la requête avec sa clé d’API (“keyId”), l’algorithme (“algorithm”) et les en-têtes HTTP (“headers”) utilisés pour créer la signature ainsi que la signature (“signature”) de la requête dans l’en-tête HTTP “Authorization”;
  • L’API vérifie l’identité de l’application via la clé d’API et vérifie la signature de la requête à l’aide de la clé secrète afin d’authentifier l’application.

La clé d’API seule n’est pas utilisable, elle permet uniquement d’identifier une application donc dans le cas où un utilisateur malveillant dérobe une clé d’API, celui-ci ne pourra rien faire sans la clé secrète qui permet, elle de garantir la preuve de l’identité de l’application via la signature des requêtes.

La clé secrète ne doit donc jamais être transmise dans une requête, l’application doit sauvegarder celle-ci de manière sécurisée.

Conclusion

On a vu tout au long de cet article, différentes manières de sécuriser une API REST. Voici les points importants à retenir :

  • L’utilisation du protocole HTTPS pour chiffrer les échanges entre le client et le serveur;
  • L’utilisation des JSON Web Token (JWT) pour l’authentification des utilisateurs. Attention à ne pas définir une durée de validité trop élevée pour le JWT dans le cas d’échanges de données sensibles;
  • L’utilisation de clé d’API et d’une clé secrète partagée pour les échanges entre une application et votre API REST. La clé secrète permettra de signer vos requêtes et d’authentifier l’application.

Cet article n’est bien entendu pas exhaustif, il existe d’autre manière de sécuriser les échanges d’une API REST (utilisation de OAuth par exemple), mais celui-ci constitue un bon point de départ. Si vous souhaitez que je rédige un article pour l’implémentation en Node.js de ces recommandations de sécurité, n’hésitez pas à me le dire en commentaire ou sur Twitter .

7 réflexions au sujet de « Sécuriser une API REST (Partie 1) : Théorie »

  1. Salut,

    Je cherchais un article dans ce genre depuis longtemps, c’était très instructif.
    Je trouve un peu dommage et “court” la partie dédiée au api-key utilisables avec une application.

    Je m’interroge d’ailleurs sur la sécurité effective via la clé secrète, puisqu’on a vite besoin de communiquer cette clé secrète et l’api key lorsqu’un client veut utiliser notre api pour son application.
    Si quelqu’un intercepte les informations à ce moment précis, on ne sécurise pas plus qu’avec le simple usage de l’api key, non ?
    Donc il doit me manquer une subtilité que je n’ai pas encore comprise…

    Merci !

    1. Salut,
      Le client doit garder précieusement sa clé secrète et ne jamais la transmettre. La clé secrète va servir à signer la requête côté client et vérifier la signature de la requête côté serveur (pour rappel la clé API permet d’identifier le client et la signature de la requête d’authentifier le client). Maintenant, comment transmettre cette clé secrète de manière sécurisée ? ça c’est à toi de voir, mais si on part du principe que c’est le serveur qui l’a transmet via le protocole HTTP, tu dois sécuriser la requête en y ajoutant une couche de chiffrement (HTTPS). Tu as la même problématique lorsque tu te connectes à un site internet, ton mot de passe doit bien être transmis au serveur. Après rien ne t’empêche de ne transmettre la clé secrète qu’une seule fois, si le client perd cette clé secrète il devra redemander un couple clé API/clé secrète. Tu peux également mettre en place l’algorithme de Diffie-Hellman qui permettra au client et au serveur de se mettre d’accord sur la clé secrète sans jamais transmettre celle-ci: Lien wikipédia et Vidéo explicative mais personnellement je me contente d’utiliser le protocole HTTPS. J’espère avoir répondu à ta question 😉

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.