JWT est un jeton permettant d’échanger des informations de manière sécurisée (RFC 7519).
Un Json Web Token est un standard définissant un moyen sûr et compact de transmettre des informations entre plusieurs tiers.
Il est sûr pour les raisons suivantes :
Le JWT est composé de 3 parties, chacune contenant des informations différentes :
claims
Le détail du standard se trouve ici : RFC 7519
La signature permet d’en vérifier la légitimité. JWT est souvent utilisé pour offrir une authentification stateless (sans état). De nombreuses librairies permettent de manipuler ces jetons (tokens) évitant ainsi l’écriture d’un code personnel pouvant donner lieu à des vulnérabilités.
Nous allons voir plus loin qu’avec l’aide du framework Spring Boot et de JWT, nous implémenterons simplement un mécanisme d’authentification stateless. Il faudra aussi aborder la révocation des tokens qui peut engendrer des problèmes de sécurité.
L’information est échangée sous la forme d’un jeton signé afin de pouvoir en vérifier la légitimité. JWT est couramment utilisé pour implémenter des mécanismes d’authentification stateless pour des SPA (Single Page Application) ou pour des application mobiles.
Le jeton est compact et peut être inclus dans une URL sans poser de problème.
Il sert à identifier l’algorithme utilisé pour générer la signature ainsi que le type de token (JWT ou un autre type d’objet).
Exemple de header :
{ "alg": "HS256", "typ": "JWT" }
Ci-dessus, le header indique que la signature a été générée en utilisant HMAC-SHA256.
Le payload est la partie du token qui contient les informations que l’on souhaite transmettre. Ces informations sont appelées “claims”. Il est possible d’ajouter au token les claims que l’on souhaite, mais un certain nombre de claims sont déjà prévus dans les spécifications de JWT comme vous avez pu le constater grâce à la section 4.1 du RFC-7519.
Par exemple, sub (pour subject) identifie le sujet du token, iss (pour issuer) va permettre d’identifier l’émetteur du token ou encore exp (pour expiration date) qui indique la date d’expiration du token. Il est fortement conseillé d’assigner une valeur à ce dernier champ afin de limiter la durée de vie du token. Si la date d’expiration est dépassée, le token sera rejeté.
{ "sub": "Robin des Bois", "exp": "1385768104", "admin": "true" }
Ci-dessous, vous constatez que l’on a ajouté admin avec la valeur true.
Elle est créée à partir du header, du payload générés et d’un HMAC secret.
La signature est la dernière partie du token. Lorsque celle-ci est invalide, elle engendre un rejet du token !
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
Une fois ces 3 éléments générés, on peut assembler notre token JWT :
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)
En reprenant les exemples précédents, on arrive au résultat suivant :
Header: { "alg": "HS256", "typ": "JWT" } Payload: { "sub": "Robin des Bois", "exp": 1385768104, "admin": true }
Voici le token généré avec le secret key laposte-promo5 (pour la signature HMAC) :
Le tout est encodé en base64-URL pour éviter les problèmes dûs aux différentes utilisations d’encodage suivant les systèmes d’exploitation et les pays.
Testez vous-même sur le site de JWT ou celui-ci
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJSb2JpbiBkZXMgQm9pcyIsImV4cCI6MTM4NTc2ODEwNCwiYWRtaW4iOnRydWV9.Hixa98JmdhlV2WNGnYW3BiMLHe_GPWfX0njQ8G7yB4Y
soit : Header . Payload . Signature
version décomposée :
Maintenant que l’on a notre token, utilisons-le pour nous authentifier !
L’authentification à base de JWT fonctionne avec les acteurs suivants :
Si on regarde la chronologie des actions, on a :
Cela fonctionne parce que le serveur d’authentification et le serveur d’application sont les seuls capables de vérifier la validité du JWT !
En effet, le JWT est lisible par le requêteur car son contenu est en base64-URL. Cependant, il n’est pas capable de générer la signature du token car il ne connaît pas la clé permettant de la générer. Il doit donc forcément être reconnu par le serveur d’authentification (avec login + mdp) pour pouvoir obtenir un JWT valide.
Plusieurs types de signatures sont possibles pour créer un JWT. Ils ont chacun leurs avantages et leurs inconvénients.
Il existe de nombreuses librairies permettant de créer et manipuler les JWT. Il est déconseillé de manipuler un JWT avec son propre code ! Une liste non exhaustive de librairies est disponible à cette adresse https://jwt.io/libraries?language=Java
Pour utiliser notre token, il faut tout d’abord le créer. Pour cela, il est nécessaire de s’authentifier avec son login et son mot de passe auprès de l’application afin que celle-ci nous renvoie le token. Une fois le token obtenu, on peut faire appel à nos URL sécurisées en envoyant le token avec notre requête. La méthode la plus courante pour envoyer le token est de l’envoyer à travers l’en-tête HTTP Authorization en tant que **Bearer ** :
voir l’introduction sur le site https://jwt.io/introduction
Authorization: Bearer ‘token’
Les fonctions de hachage sont des fonctions mathématiques qui permettent de calculer une empreinte d’une donnée. Ces fonctions génèrent une chaîne de caractère à partir d’une donnée fournie en entrée.
Les caractéristiques des fonctions de hachage sont les suivantes :
Grâce à ses caractéristiques, le hash peut être utilisé comme signature d’une donnée.
adresse du site pour les tests : https://bcrypt-generator.com/
Pour plus de détails, consultez la définition de Wikipedia des fonctions de hachage cryptographique
La signature HMAC est très simple à mettre en place. Il suffit au serveur d’authentification de hasher le header et le payload en y ajoutant un secret (une chaîne de caractères).
Si plusieurs serveurs d’application utilisent le même JWT, alors il faut qu’ils partagent tous le même secret pour être en mesure de vérifier la validité du JWT. Cela augmente les chances de vol de secret et complexifie le changement du secret (à chaque changement, il faut informer tous les serveurs d’application par voie sécurisée).
De même, si le secret est trop simple alors les attaques par force brute pour le deviner peuvent être facile.
La signature par RSA est plus sûre que la signature HMAC. En effet il est quasiment impossible de tenter des attaques par force brute (pour le moment).
Elle est aussi plus sûre car la mise en oeuvre du RSA permet de distinguer la génération des JWT et leur vérification (grâce à la combinaison clé publique / clé privée). En effet, ce sera le serveur d’authentification qui va générer la paire de clé permettant de chiffrer et déchiffrer la signature.
Pour le comprendre, il faut savoir que les méthodes de chiffrement asymétriques (comme RSA) permettent deux choses : de chiffrer des données ou de signer des données. Ceci dépend du choix de clé qui est fait lors du chiffrement d’un message. Les raisons du fonctionnement de ces méthodes de chiffrement asymétriques sont mathématiques. Il convient juste de retenir ici qui ces méhodes fonctionnent avec deux clés qui sont liées par des propriétés mathématiques : une clé publique qui est disponible pour tout le monde et une clé privée que seul le créateur de la paire de clé doit connaître.
Pour illustrer ces différents cas d’utilisation on parlera toujours d’Alice et de Bob qui souhaitent communiquer ensemble. Dans les deux cas, Alice va générer une paire de clés et va transmettre sa clé publique à Bob.
Cas #1 : Chiffrement - Alice souhaite recevoir un message chiffré de la part de Bob.
Bob va écrire son message, puis le chiffrer avec la clé publique d’Alice. Bob peut envoyer son message sereinement car il est sûr que seule Alice peut le déchiffrer. En effet, seule la clé privée connue d’Alice pourra transformer le message chiffré de Bob en un message lisible pour un humain.
Cas #2 : Signature - Bob souhaite vérifier qu’un message envoyé par Alice vient vraiment d’Alice.
Alice va écrire son message, le hasher puis chiffrer le résultat du hash du message avec sa clé privée. Le résultat du hash chiffré sera la signature qu’Alice ajoutera à la fin de son message. Lorsque Bob va recevoir le message d’Alice, il va lui aussi hasher le message reçu et va déchiffrer la signature d’Alice grâce à la clé publique. Si le résultat du hash correspond au déchiffrement de la signature alors Bob peut être sûr que le message a bien été envoyé par Alice.
Dans le cas de l’utilisation de JWT, nous serons dans le cas #2.
Le serveur d’authentification peut être comparé à Alice :
Le(s) serveur(s) d’application peut être comparé à Bob :
Si le résultat du hash correspond à la signature déchiffrée alors le serveur d’application peut être sûr que l’utilisateur a bien été authentifié par le serveur d’authentification.
Comme la clé publique est distribuée à tous les serveurs d’application et qu’elle n’est pas confidentielle, il n’y a pas de risque de vol. Il est aussi plus facile de changer la paire de clé. Cela permet en plus de séparer les rôles. Ici, seul le serveur d’authentification peut générer les JWT.
La signature RSA est un peu plus complexe à mettre en place que la signature HMAC.
Il nous reste à voir son application avec Spring Boot…