Principes de sécurité des JWT Tokens
Les JWT ne sont ni encryptés, ni hachés !
Lorsqu’ils sont transmis entre client et serveur, les JWT sont encodés en Base64. Cela signifie qu’ils sont convertis dans un format “facile à transmettre”. Cet encodage n’offre cependant aucune sécurité (contrairement à un encryptage ou un hachage).
Pour autant, cela ne veut pas dire que les JWT ne sont pas sécurisés, et heureusement d’ailleurs ! En réalité, la principale sécurité d’un JWT repose sur sa signature que seul un serveur connaissant la clé secrète pourra vérifier.
Les JWT sont des jetons signés
Comme expliqué précédemment, la troisième partie d’un JWT est la Signature, qui est générée par un algorithme de hachage à partir de l’intégralité des informations du token (i.e. à partir de son Header et de son Payload) et d’une clé secrète connue uniquement du serveur. Cette fonction de hachage va ainsi générer un hash (chaîne de caractères) stockée dans le JWT lui-même : c’est sa Signature.
A chaque fois que le serveur recevra une requête contenant un JWT, il re-générera la Signature à partir des Header & Payload du JWT et de sa clé secrète. Si le hash obtenu est identique à la signature du jeton : le JWT est bien valide ! Ainsi, seul le serveur ayant connaissance de cette clé secrète sera capable de déterminer si le jeton communiqué par un client est bien valide ou non 😃
Par exemple, reprenons notre JWT signé par la clé « CLE-SECRETE » :
A présent, voici ce qu’il se passe si je modifie la clé secrète du serveur par « CLE-INVALIDE » :
La signature n’est plus valide ! De même, on observe un comportement similaire si la signature est la bonne mais que l’on modifie la partie Payload de notre JWT encodé en Base64 (j’ai ici modifié le JWT de façon à avoir l’id utilisateur “5678” au lieu de “1234”) :
Les Claims : des paramètres de sécurité
Comme indiqué précédemment, le Payload de nos JWT peut non seulement contenir des informations personnalisées, mais il peut également contenir un certain nombre de paramètres prédéfinis (i.e. spécifiés dans la documentation RFC 7519 de JWT) appelés Claims. Ces paramètres sont essentiellement liés à la sécurité du jeton.
Expiration time
Les JWTs peuvent avoir une durée de vie, et je vous recommande fortement d’en préciser une par précaution / pour des raisons de sécurité avec le paramètre exp du JWT.
Puisque la date d’expiration d’un token est indiquée dans son Payload, celle-ci doit être déterminée lors de la création du JWT, côté serveur. On fixe généralement la durée d’un token à quelques dizaines de minutes au maximum (très souvent 15 minutes). Il reste toutefois possible de fixer cette durée à plusieurs heures : tout dépend du niveau de sécurité souhaité.
Formellement, cette date est indiquée dans le paramètre exp par un timestamp : le nombre de secondes depuis Epoch (i.e. le 1er Janvier 1970).
Issued at
Dans la même idée que exp, le paramètre "Issued at" (iat) permet de savoir précisément quand le jeton a été généré. Il est également représenté par le nombre de secondes depuis Epoch.
Not before
Le paramètre "Not before" (nbf) indique à partir de quelle date le JWT commencera à être valide. Cela permet de ne pas rendre le jeton valide dès sa génération mais seulement à partir d’une date précise, donnée elle aussi en secondes depuis Epoch comme pour exp et iat.
Issuer
L’"Issuer" (iss) permet de savoir qui a généré ce jeton. On peut par exemple y placer le nom ou l'URL du serveur, du service ou de l’application qui a généré le JWT.
Subject
Le "Subject" (sub) permet de savoir ou pour qui a été généré le jeton. On peut par exemple y placer l’identifiant de l’utilisateur connecté.
Audience
Le paramètre aud indique l'audience à laquelle est destiné ce JWT. Cela permet, par exemple, de distinguer une audience « dev » (développement) d’une audience « prod » (production) lors du développement d’une application. On peut également s’en servir pour indiquer à quel « type » d’audience le jeton appartient : “application-mobile”, “application-web”, “client-logiciel”, etc.
JWT ID
Le JWT ID (jti) permet simplement d’attribuer un identifiant unique à un jeton.
En savoir plus
Pour plus d’informations sur tous ces paramètres, je vous invite à faire un tour sur la spécification de JWT.
Et si un JWT se fait dérober ?
Hypothèse : un client envoie son JWT dans chaque requête à un serveur. Malheureusement, un hacker se trouve sur ce réseau et intercepte une requête HTTP contenant le JWT. Pourra-t-il alors récupérer le JWT et se faire passer pour un utilisateur légitime auprès de notre serveur ?
En théorie, oui !
Petit rappel : un JWT n’est pas encrypté. Il est simplement encodé en Base64. Cela signifie que si une tierce personne parvient à dérober le JWT d’un utilisateur, il pourra non seulement se faire passer pour cet utilisateur auprès de notre serveur mais il pourra aussi voir toutes les informations que ce JWT contient.
Pas d’inquiétude, les JWT sont bien sécurisés. En fait, un JWT n’est pas un protocole d’authentification. C’est simplement une spécification du format de jeton sécurisé. Cela signifie qu’il faut transmettre les jetons JWT à travers un protocole d’échange sécurisé ! Et lorsqu’on parle d’architecture client/serveur, d’APIs et de requêtes HTTP, c’est forcément à HTTPS que l’on fait référence.
Ainsi, à partir du moment où votre jeton sera transmis par un réseau sécurisé (i.e. encrypté), le risque qu’un JWT soit dérobé est infime.
Et si un hacker trouve ma clé secrète ?
La clé secrète ne s’appelle pas “clé secrète” pour rien. Si celle-ci était découverte, vous risquez d’importants problèmes de sécurité. Mais pas d’inquiétude, tout n’est pas encore perdu : vous n’avez qu’à modifier votre clé secrète et votre système de génération & vérification de JWT sera à nouveau fonctionnel ! La seule conséquence de la remise à zéro de la clé secrète est que tous les JWT précédemment générés seront considérés comme invalides par votre serveur. Autrement dit, tous vos utilisateurs connectés au moment du changement de la clé secrète devront se reconnecter : c’est un (très petit) mal pour un bien.