Cet article a pour but de présenter ce que sont les JWT, quand on y est confronté. Comme on le verra, on ne fera pas sciemment le choix d’utiliser JWT dans un projet, et plus important encore : on n’utilisera pas de JWT comme jeton de session.

# Qu’est-ce que c’est ? 

JSON Web Token (JWT) est une représentation compacte et URL-safe (peut être passée aisément dans une query-string par exemple) de données à transférer entre deux parties. Les données sont codées sous la forme d’un objet JSON, qui sera signé et/ou chiffré.

Voici, paraphrasée, la définition du standard IETF qui le définit (RFC 7519).

# A quoi ça sert ? A quoi ça peut servir ? 

Le but est donc de transférer des données, avec certaines garanties (ou non d’ailleurs) : authenticité, intégrité, voire confidentialité (si le message est chiffré). Les usages sont donc multiples.

JWT est ainsi utilisé dans OpenID Connect pour coder l’ID Token qui transfert à l’application des informations sur le processus d’authentification qui a eu lieu au niveau du serveur d’identité. OpenID Connect utilise également des JWT pour coder des aggregated claims : des informations provenant d’autres serveurs d’identité, et dont on voudra donc vérifier l’authenticité et l’intégrité.

Un JWT peut être utilisé pour s’authentifier auprès d’un serveur, comme avec le profil JWT Bearer d’OAuth 2 (RFC 7523).

Toujours dans le monde OAuth 2, les access tokens OAuth 2 peuvent être eux-même des JWT (RFC 9068), les paramètres d’une demande d’autorisation OAuth 2 peuvent être codés dans un JWT (RFC 9101), de même que les réponses d’introspection de tokens (draft IETF : JWT Response for OAuth Token Introspection), et enfin l’enregistrement dynamique de clients utilise des JWT pour identifier le logiciel dont l’installation tente de s’enregistrer (les software statements dans RFC 7591 : OAuth 2.0 Dynamic Client Registration Protocol).

# Comment ça marche ? 

Un JWT est composé d’au moins 2 parties, séparées par un . (point), la première étant toujours l’entête. Chaque partie est toujours codée en base64url, une variante du codage base64 avec notamment les caractères + et / (qui sont particuliers dans les URLs) remplacés respectivement par - et _, et sans caractères = finals.

Il existe deux types de JWT : la JSON Web Signature (JWS, définie par la RFC 7515), et la JSON Web Encryption (JWE, définie par la RFC 7516). Le cas le plus courant est la JWS, composée de 2 à 3 parties : l’entête, le corps, et la signature (optionnelle). Le cas des JWE est plus rare (et complexe) et ne sera pas abordé ici.

L’entête, commune aux deux cas, décrit le type de JWT (JWS ou JWE) ainsi que les différents algorithmes de signature, MAC, ou chiffrement utilisés (codifiés par la RFC 7518) et autres informations utiles, sous la forme d’un objet JSON.
Dans le cas d’une JWS, on aura ainsi par exemple l’algorithme de signature ou de MAC, un éventuel identifiant de clé (quand plusieurs clés peuvent être utilisées, notamment pour permettre la rotation des clés), voire une URL où trouver les informations sur les dites clés (au format JWKS, défini par la RFC 7517), etc.

Dans le cas d’une JWS, le corps (payload) sera généralement l’objet JSON représentant les données transférées (mais pourrait techniquement être un autre JWT).

La troisième partie est la signature ou le MAC. Cette partie est absente si l’entête indique que le JWT n’est pas protégé ("alg":"none").

Pour le debug, on pourra utiliser le JWT Debugger d’Auth0 pour décoder des JWT (attention à ne pas l’utiliser sur des données sensibles, uniquement sur des JWT provenants de serveurs de tests)

Les JWT étant presque toujours utilisés dans des contextes de sécurité, ils sont à manier avec précaution, notamment concernant les composantes cryptographiques.

Il est impératif d’utiliser des bibliothèques spécialisées pour manipuler les JWT, et faire attention à correctement les utiliser pour éviter toute faille de sécurité.

On pourra se référer à la RFC 8725 qui décrit un ensemble de bonnes pratiques dans la manipulation et l’utilisation des JWT.

# Critiques 

De nombreux experts en sécurité, et notamment des cryptographes, critiquent vertement les JWT et déconseillent leur utilisation.

La principale critique est sa complexité, alors même qu’il pourra apparaître comme simple pour des développeurs :

  • pour commencer, on a besoin de savoir décoder de l’UTF-8 et du JSON, et donc autant de sources de bugs (et donc de potentielles vulnérabilités) ;

  • et bien entendu puisqu’on a un format générique qui permet de signer et/ou chiffrer, voire rien de tout ça ("algo":"none"), avec une liste d’algorithmes longue comme le bras, il faut être capable de gérer un grand nombre de cas (même si c’est pour les refuser).

  • Puisque le JWT déclare lui-même l’algorithme utilisé pour le signer ou le chiffrer, le logiciel qui reçoit un JWT doit donc lui faire en partie confiance, ou correctement vérifier l’algorithme utilisé par rapport à une liste d’algorithmes autorisés. À cause de son apparente simplicité, de nombreuses bibliothèques ont fleuri qui n’incluaient pas les vérifications nécessaires et acceptaient sans problème des JWT non signés (avec "alg":"none"), permettant à un attaquant d’utiliser n’importe quel JWT, sans vérification d’authenticité ou d’intégrité. Et aussi invraissemblable que ça puisse paraître, on continue de découvrir des applications vulnérables !

    À noter : de la même manière, l’entête peut inclure directement la clé publique à utiliser pour vérifier la signature ; si on l’utilise pour vérifier la signature, on aura prouvé l’intégrité du JWT, mais pas son authenticité puisque la signature aura pu être générée par n’importe qui.

  • Une autre attaque consiste à utiliser la clé publique destinée à vérifier une signature asymmétrique ("alg":"RS256" ou "alg":"ES256") comme clé d’un MAC ("alg":"HS256") : l’application qui reçoit le JWT pourrait alors, par erreur, valider le MAC et autoriser le JWT. N’importe qui pourrait alors créer un JWS qui sera accepté par l’application, alors que celle-ci pense vérifier une signature asymmétrique.

    Cette vulnérabilité peut être dûe à une mauvaise utilisation de la bibliothèque de vérification des JWT, mais également dans certains cas directement liée à l’API de cette bibliothèque qui ne fait pas la différence entre une clé publique et un secret symmétrique (généralement par soucis d’être facile d’utilisation).

    Bien que les ID Token d’OpenID Connect soient des JWT, on n’aura pas besoin de vérifier leur signature puisqu’on les obtient la plupart du temps au travers d’une communication HTTPS qui garantit déjà l’authenticité et l’intégrité (et la confidentialité) du message, ce qui nous permet d’éviter toutes ces classes de vulnérabilités.

    Une autre critique est liée au mésusage des JWT, souvent par manque de connaissance/compétence en matière de sécurité : la validité d’un JWT est directement vérifiable, sans avoir besoin d’une base de données de jetons valides ou de service de validation (l’authenticité et l’intégrité du JWT sont vérifiables, donc les dates de validité qu’il contient sont fiables), mais ça rend le JWT impossible à révoquer (sauf à ajouter un tel mécanisme –par exemple basé sur le claim jti, prévu par la spécification pour l’anti-rejeu– qui met à mal la raison même pour laquelle on aura choisi en général le format JWT au départ). Si un JWT est utilisé comme jeton de session par exemple, il est alors impossible de se déconnecter ou mettre fin à une session.
    Dans la plupart des cas d’utilisation (dans les spécifications), les JWT sont validés et utilisés dès réception par l’émetteur, donc la question de la révocation ne se pose pas vraiment. C’est lorsqu’un JWT est stocké par le receveur pour un autre usage futur que le problème se présente (comme par exemple avec un jeton de session ou un access token).

    Quelques articles critiques des JWT :