JWT: faut-il inclure les permissions dans le jeton?

Introduction

JWT

JWT, pour JSON Web Token, est un standard décrivant une structure de données permettant l’authentification d’une personne ou d’un service. En obtenant une instance d’une telle structure (appelée jeton) auprès d’un mécanisme capable de le générer, vous pouvez montrer patte blanche auprès de services Web afin qu’ils accèdent à vos requêtes.

Identification et authentification

Le contenu de la « charge utile » du jeton est laissé à la discrétion du développeur de l’application. Le standard propose plusieurs champs (appelés claims) qu’un jeton peut contenir afin de spécifier son rôle et ses conditions d’utilisation. On y trouve notamment l’identifiant de l’entité l’ayant fabriqué, son destinataire ou encore sa période de validité.

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

L’identifiant de son propriétaire figure presque systématiquement dans la charge utile. On peut également inscrire des renseignements utiles tels que son adresse e-mail ou une URL menant à sa photo de profil. Les jetons étant signés par leur émetteur, l’identifiant du propriétaire permet non seulement son identification (savoir qui il est) mais aussi son authentification (prouver qu’il s’agit bien de lui). Le vol d’un jeton (que son contenu soit chiffré ou non) permettant de se faire passer pour son propriétaire légitime, il est fortement recommandé de ne transmettre celui-ci que sur des canaux sécurisés, par exemple en utilisant TLS.

Autorisation

Un des usages les plus répandus de JWT est la gestion des autorisations, c’est-à-dire la détermination du droit pour le propriétaire du jeton d’accéder à une ressource ou d’exploiter un service.

Deux camps s’opposent traditionnellement. Le premier propose de n’inscrire dans le jeton que l’identifiant de son propriétaire, tandis que le second propose d’y ajouter les informations nécessaires à l’autorisation, par exemple sous la forme d’une liste des permissions rattachées au propriétaire, comme illustré ci-dessous.

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "permissions": ["read_article", "write_article", "send_mail"]
}

Dans cet article, je propose de discuter des avantages et inconvénients des deux approches.

Approche intégrée

L’idée centrale de l’approche intégrée est que le jeton est entièrement suffisant pour gérer l’autorisation. En y lisant les permissions dont dispose son propriétaire, un service peut déterminer si celui-ci peut accéder à la ressource ou à la fonctionnalité demandée, sans devoir consulter une base de données ou un service tiers pour trancher. C’est donc une approche efficace d’un point de vue opérationnel, car elle limite les échanges entre les composants applicatifs.

Il faut cependant prendre en compte la taille du jeton, qui augmente avec le nombre de permissions associées au propriétaire. Si cela peut sembler anodin, il faut se souvenir que le jeton est transmis avec chaque demande à un service. De plus, la liste des permissions s’étoffe lorsque l’application grandit, ce qui limite la capacité de passage à l’échelle de cette approche.

Si les permissions sont inscrites dans le jeton, il est possible pour un observateur malveillant d’en déduire le système d’autorisation de l’application et les privilèges particuliers que possèdent le propriétaire du jeton observé. Cela lui permet de mieux cibler son attaque, par exemple en concentrant ses efforts sur les administrateurs de l’application. Il est alors fortement conseillé de chiffrer les jetons. C’est de toute façon une bonne pratique, mais elle n’est que rarement mise en œuvre, si bien que les utilisateurs se trouvent davantage exposés lorsque leurs permissions sont jointes à leurs jetons.

Une fois un jeton émis, il est considéré comme immuable (son contenu ne peut être modifié), car dès qu’un service en a pris connaissance, il n’est plus possible de lui faire oublier son contenu. Si on ajoute une permission à un utilisateur, celui-ci doit demander et exploiter un nouveau jeton (par exemple en se déconnectant puis en se réauthentifiant sur un site Web), ce qui est peu pratique. Lorsqu’une permission est retirée à l’utilisateur, ce changement ne prendra pas effet tant que l’utilisateur utilisera son ancien jeton.

Afin de réduire l’ampleur de ce problème, on peut limiter la validité d’un jeton à quelques minutes ou quelques heures. Pour éviter que l’utilisateur ne doive se réauthentifier trop fréquemment, le générateur de jeton peut y inscrire une durée de validité de renouvellement de longue durée (par exemple, un jour ou une semaine). Ainsi, lorsque son jeton vient à expirer, l’utilisateur peut demander son renouvellement automatique au générateur, qui en profite pour mettre à jour la liste des permissions.

Approche minimaliste

En n’indiquant dans ses jetons que l’identifiant de l’utilisateur, l’approche minimaliste fait une distinction nette entre l’authentification et l’autorisation (en ignorant cette dernière). Cela évite les problèmes de cache relatifs aux permissions dont souffre la première approche et garantit une taille pratiquement constante et toujours raisonnable des jetons.

Dans ce cas, il incombe alors aux services de gérer l’autorisation de l’utilisateur. Cela pourrait être réalisé en ayant recours à un service dédié, mais cela impliquerait soit un appel à ce service chaque fois qu’un service est appelé (doublant effectivement les besoins en bande passante du réseau à la seule fin de l’autorisation), soit la mise en place d’un cache des permissions, ce qui réintroduit le problème de péremption.

Lorsque l’application est organisée en microservices, une autre option est possible, et s’impose même. Elle consiste à considérer que chaque service est responsable de la gestion des permissions qui le concernent. Aucun autre service, y compris celui de génération de jetons, ne doit se préoccuper des détails de permission des autres. Chaque service peut également implémenter cette gestion de la manière la plus appropriée pour lui, allant d’une exposition sans permission particulière à l’appel à un service tiers, en passant par l’usage d’une base de données interne ou la définition d’une liste des identifiants d’utilisateurs autorisés dans un fichier de configuration. Cette souplesse et cette autonomie favorise l’évolutivité de l’application, au détriment d’une plus grande charge opérationnelle.

Révocation de jeton

L’utilisation de jetons ayant une courte durée de validité peut être profitable aux deux approches, pour des raisons de sécurité: on souhaite éviter qu’un jeton compromis ne puisse causer trop longtemps de torts. Par exemple, l’utilisateur peut souhaiter pouvoir se déconnecter de l’application, de sorte qu’il soit obligé de se réauthentifier lors de sa prochaine session d’utilisation. Il peut même souhaiter que cette déconnexion se répercute sur toutes les machines à partir desquelles il a pu se connecter, pour éviter une usurpation d’identité.

Une manière de procéder consiste à permettre le renouvellement automatique du jeton au moyen d’un identifiant unique intégré au jeton. Lors de la génération du premier jeton, le système génère également cet identifiant et en garde une copie. Lorsqu’une demande de renouvellement de jeton lui parvient, le système vérifie s’il connaît toujours l’identifiant unique et s’il est toujours valide. Si c’est le cas, un nouveau jeton est généré avec un nouvel identifiant, et l’ancien est supprimé.

Si l’utilisateur souhaite se déconnecter ou si on suspecte la compromission de son jeton, on en demande la révocation auprès du service de génération de jetons. Celui-ci supprime simplement l’identifiant unique de sa base de données, de sorte que le jeton concerné ne pourra plus être renouvelé.

Alternativement, le système peut maintenir une liste des jetons révoqués. Ils pourront être retirés de cette liste après leur expiration.

Conclusion

Nous avons discuté de deux approches permettant la gestion de l’autorisation par le biais de jetons de type JWT. Chaque approche a ses avantages et inconvénients, aussi devez-vous les évaluer en fonction des besoins de votre projet.

Nous nous sommes également intéressés à certains de leurs points communs, et notamment à un mécanisme de révocation de jetons.

Personnellement, j’ai tendance à attacher une grande importance à l’évolutivité des applications, ce qui m’incite à favoriser l’approche minimaliste. J’y trouve une forme d’élégance, qui n’est, en fin de compte, qu’une énième émanation du principe de séparation des responsabilités.

2 réflexions sur “JWT: faut-il inclure les permissions dans le jeton?

  1. Pingback: Vous reprendez bien un peu de curry avec votre programme? | Le compas de l'architecte

  2. Pingback: Un système de vérification de licence logicielle simple et efficace | Le compas de l'architecte

Répondre à Un système de vérification de licence logicielle simple et efficace | Le compas de l'architecte Annuler la réponse.