Gérer l'authentification d'une API REST

Les API REST, souvent basées sur du JSON transporté par HTTP, sont à la mode depuis bien longtemps. Et lorsque l'on en développe une, il est bien normal de vouloir authentifier les clients qui réalisent des requêtes. Pour cela beaucoup de solution existent, mais malheureusement toutes ne se valent pas. Nous verrons ainsi une première solution simple, sinon simpliste, puis une seconde solution qui, tout en restant simple, se trouve être bien plus robuste.

La clé API dans une en-tête HTTP

On voit ça presque partout : le client dispose d'une clé API qu'il met simplement dans une en-tête HTTP et hop, le tour est joué ! Simple, rapide et efficace, alors pourquoi se casser la tête à vouloir faire autrement ? Le flux HTTP est protégé par TLS, donc (presque) aucun risque d'interception. C'est pour ça que ce procédé est autant répandu : en plus d’être d'une simplicité enfantine, il n'est pas intrinsèquement rongé par un problème de sécurité.

Cependant, votre API recevra bien très certainement des appels de clients légers automatisés qui ont, pour des raisons évidente de gestion des erreurs, une forte tendance à journaliser chaque requête et réponse dans les moindres détails. Avec une clé API qui se balade dans chaque requête et des logs potentiellement verbeux nous avons un risque non négligeable de fuite de la dite clé, pourtant point critique de l'authentification.

Mais ne nous affolons pas pour autant, ce n'est pas dramatique. Disons simplement qu'il y a mieux à faire, aussi bien du point de vue de la sécurité que des possibilités techniques.

Une autre raison un peu plus importante de s'inquiéter réside dans les proxy d'entreprise ou tout autre type de logiciels d'interception des flux HTTPS. Si le client, par exemple un navigateur web, est contraint de passer par un tel logiciel, alors la personne contrôlant ce logiciel sera en mesure de modifier le contenu des requêtes et aura également accès à la clé API devant normalement rester secrète. En plus du risque direct de manipulation, nous avons là encore un nouveau risque de fuite de la clé via des journaux trop verbeux sur le proxy (les DSI adorent ça).

Encore une fois rien de vraiment dramatique, mais si l'on pouvait faire mieux sans trop se fatiguer, ce serait bien.

JSON Web Signature et ses dérivés

En 2015 sont sorties plusieurs RFC dont l'objet est de proposer un standard de signature et de chiffrement de données dont le résultat est représenté en JSON.

  • RFC 7515: JSON Web Signature (JWS)
  • RFC 7516: JSON Web Encryption (JWE)
  • RFC 7517: JSON Web Key (JWK)
  • RFC 7518: JSON Web Algorithms (JWA)
  • RFC 7519: JSON Web Token (JWT)
  • RFC 7520: Examples of Protecting Content Using JSON Object Signing and Encryption (JOSE)

Ce nombre assez grand de RFC a de quoi intimider mais a l'avantage de bien segmenter en briques distinctes, permettant ainsi d'utiliser indépendamment chacune d'entre elles. Au lieux d'un gros morceau indigeste, on se retrouve donc avec plusieurs petits standards prévus spécifiquement pour interagir entre eux et pouvant être mis en œuvre de manière totalement indépendante.

Mais détaillons donc un peu ces standards afin d'y voir plus clair. Tout d'abord, ce qui nous intéresse dans le cas présent : JWS. C'est lui qui défini la structure même de la signature de données quelconques et donc ce que nous allons utiliser afin de signer chacune de nos requêtes à l'API. Ensuite nous avons JWE, similaire à JWS mais traitant cette fois de chiffrement des données. Fort utile dans certains cas, mais pas dans le notre si l'on utilise HTTPS. Des exemples de mise en œuvre de JWS et JWE sont donnés dans la dernière de ces RFC.

Viennent ensuite les briques sur lesquelles se reposent JWS et JWE. Tout d'abord JWK qui permet de représenter des clés publiques et privées en JSON, puis JWA qui défini les algorithmes de signature et de chiffrement utilisés.

Enfin nous avons JWT qui utilise JWS et JWE pour définir un format de jetons d'authentification principalement destinés à l'authentification unique de navigateurs web. Nous ne traiterons pas de ce standard dans ce billet.

Extensions

Bien entendu, ces standards sont extensibles. Par exemple, dans le cas de notre API REST, il peut sembler inconvenant de coder la charge (notre requête) en base64url, il serait plus lisible de la conserver directement en JSON. Bien qu'impossible dans le standard de base, la RFC 7797 ajoute un nouveau paramètre nous permettant de procéder ainsi.

Dans un autre genre, la RFC 8037 et la RFC 8812 ajoutent de nouveaux algorithmes de signature, de chiffrement et d'échange de clés.

Afin de se retrouver dans l'ensemble des définitions (champs et valeurs) possibles utilisés dans JWS, JWE et leurs dérivés, tout est enregistré auprès de l'IANA dans un ensemble de registres regroupés sous l'unique entité JSON Object Signing and Encryption (JOSE).

Pourquoi utiliser JWS ?

Pour commencer, JWS et ses technologies associées étant des standards, il existe un grand nombre de bibliothèques écrites dans différents langages qui gèrent ça. La création de messages signés ainsi que leur vérification n'auront pas besoin d'être réimplémentés, il n'y a qu'a utiliser ce qui existe déjà.

Ensuite, le message étant intégralement signé, sa modification par une entité tierce est impossible. Si votre message comporte un nonce, ce dernier étant signé et à usage unique, vous empêcher toute attaque par rejeu. Et la clé API secrète n'étant pas transmise dans les requêtes, le risque de fuites est sérieusement réduit (mais pas éliminé). Cela fait pas mal d'améliorations très appréciables par rapport à notre première solution naïve.

À ce stade, nous notons qu'une JWS basée sur un algorithme a clé symétrique (par exemple HS256, HS384 ou HS512) est largement suffisant pour beaucoup de cas. Le client n'a alors besoin que d'un identifiant et d'une clé API (les deux étant fournis par le serveur) pour authentifier chaque requête de manière fiable.

Pour les cas d'usage qui s'y prêtent, JWS permet également l'utilisation d'algorithmes de signatures basés sur des couples de clés asymétriques. Ainsi, le protocole ACME (RFC 8555) tire partit du meilleur des deux mondes en faisant usage à la fois des signatures symétriques et asymétriques qu'offre JWS.

Tags