Les API REST sont actuellement très populaires, elles permettent de s’interfacer avec une grande variété de clients tels qu’une IHM en JavaScript, une application mobile, un ERP, etc… Elles facilitent l’implémentation d’architecture de type micro services, qui permettent de bien séparer les différentes responsabilités métier.

Toutes les briques de la chaîne ont besoin de partager un système de sécurité commun en sachant que REST est sans état. Elles auront aussi besoin d’accéder facilement aux données de sécurité.

C’est là qu’intervient le Json Web Token (rfc7519). Il s’agit d’une chaîne de caractères qui peut être partagée entre les systèmes et qui va remplir les fonctions d’identification et d’authentification dans les différentes briques. La plupart du temps, il est partagé dans l’en-tête “Autorization” des requêtes.

Composition

Le JWT est une chaîne de caractères composée de trois parties séparées par des points : l’en-tête, la charge utile et la signature.

L’en-tête

Il s’agit de la première partie du token. Il indique le format du token et un nom d’algorithme de hash. Le type sera toujours “JWT”. Pour l’algorithme, il en faut un sûr, tel que le HMAC SHA256 ou RSA.

{
  "alg": "HS256",
  "typ": "JWT"
}

Le tout est encodé en base 64 afin de faciliter le transfert, ce qui donne : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

La charge utile

C’est la deuxième partie du token. Il s’agit d’un objet JSON dans lequel on peut stocker de multiples informations. On peut y trouver les types suivants :

  • “réservées”, comme une date d’expiration du token, ce sont des informations qui vont être exploitées par la couche de sécurité de l’API.
  • “privées”, elles sont spécifiques à un système qui aurait besoin d’un supplément d’information.
  • “publiques”, c’est vous qui les définissez, vous pouvez mettre ce que vous souhaitez, généralement des données concernant l’utilisateur et ses permissions.
{
   "userId": "269351",
   "email": "test@exemple.com",
   "roles": ["PERM_SHOW_REPORTING", "PERM_SHOW_ADMIN"]
}

Le tout est encodé en base 64 afin de faciliter le transfert, ce qui donne : eyJ1c2VySWQiOiIyNjkzNTEiLCJlbWFpbCI6InRlc3RAZXhlbXBsZS5jb20iLCJyb2xlcyI6WyJQRVJNX1NIT1dfUkVQT1JUSU5HIiwiUEVSTV9TSE9XX0FETUlOIl19.

La signature

C’est la troisième partie du token. Elle permet de vérifier que les données du token transmit n’ont pas été modifiées. Elle est générée en concaténant les parties précédentes (encodées en base 64) et un secret, le tout hashé par la méthode spécifiée par alg dans la première partie du token.

Si “secret” est utilisé comme secret, voici la signature du token : 4YpMaw8PbF_t7UILdRu9ciY62kRPTF-ugeMaYvv6qv4

Le token

Une fois complet, voici le token donné en exemple : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyNjkzNTEiLCJlbWFpbCI6InRlc3RAZXhlbXBsZS5jb20iLCJyb2xlcyI6WyJQRVJNX1NIT1dfUkVQT1JUSU5HIiwiUEVSTV9TSE9XX0FETUlOIl19.4YpMaw8PbF_t7UILdRu9ciY62kRPTF-ugeMaYvv6qv4

Garder la sécurité à l’esprit

Comme toujours avec la sécurité, il faut être vigilant sur les problématiques usuelles.

Par exemple, cet article de auth0.com parle des vulnérabilités du format. Vous devez envisager de rafraichir le token à chaque utilisation et de créer une liste des tokens valides qui ont été générés pour vérifier que c’est bien de votre système qu’il est issu.

Pour que le token reste court, il faut en limiter le nombre d’informations, par exemple, l’identifiant de l’utilisateur et une liste de permission. En vue d’avoir les informations utilisateur, vous pouvez mettre en place une méthode d’API qui agrège et retourne les données utilisateurs basées sur un identifiant dans le JWT. Cette méthode devra être appelée une fois au tout début de la session ou quand il faut mettre à jour les informations de l’utilisateur.

Les tokens ne doivent pas être générés sur des systèmes non sûrs. Par exemple, dans une IHM en JavaScript. Cela signifierait que vous y avez placé votre secret (permettant de générer la signature) et qu’une personne malveillante peut donc y accéder.

Les informations du token doivent être utilisées à bon escient. Par exemple dans une IHM Javascript, il ne faut pas permettre une action juste sur la foi des données du token. Elles ne sont pas sûres étant donné que la signature n’est pas vérifiée côté JavaScript. Mais elles peuvent être utilisées pour afficher ou non un bouton.

Mise en place dans Symfony

Lexik propose un bundle LexikJWTAuthenticationBundle qui permet de générer des JWT et un fournisseur qui effectuera les vérifications de la signature. Sa procédure d’installation est bien détaillée.

Les requêtes AJAX sont soumises à des mesures de sécurité. Notamment, les navigateurs ne permettent pas l’accès à des ressources qui ne sont pas sur le même domaine (sous-domaine inclus) que le document chargé. Ce concept s’appelle : Same-origin_policy. Étant donné qu’une API et une IHM Javascript sont souvent sur des sous-domaines différents, cette règle bloque les communications. Toutefois le W3C a défini un mécanisme permettant quand même le partage, le Cross-Origin Resource Sharing ou CORS. En résumé, il s’agit d’ajouter des en-têtes dans la réponse qui listent les domaines autorisés à exploiter les ressources.

Pour vous faciliter la mise en place des en-têtes CORS, Nelmio fournit un bundle très pratique : NelmioCorsBundle.

Pour aller plus loin

Le site jwt.io propose une liste de bibliothèques et un débuggeur.

Les-tilleuls.coop vous propose API platform qui est un framework basé sur Symfony vous permettant de mettre en place une API REST hypermédia exposant des données structurées. Il supporte l’hypermédia et le Web des données (Linked Data) avec JSON-LD, Schema.org et Hydra.