Comment Décoder les Jetons JWT en Toute Sécurité
JWT (JSON Web Token) est un format de jeton compact et sûr pour les URL défini par la RFC 7519. Il est utilisé pour l'authentification et l'échange d'informations entre parties. Les JWT sont devenus le standard de facto pour représenter des revendications de manière sécurisée entre deux parties, comme un client et un serveur dans une application web moderne. Ils sont autonomes, ce qui signifie que toutes les informations nécessaires pour authentifier une requête sont intégrées directement dans le jeton lui-même, éliminant le besoin de stockage de session côté serveur dans de nombreuses architectures.
La popularité des JWT s'étend aux applications mono-page, aux applications mobiles, aux passerelles API et à la communication entre microservices. Lorsqu'un utilisateur se connecte à une application, le serveur émet un JWT que le client stocke et présente à chaque requête suivante. Le serveur vérifie ensuite la signature du jeton pour authentifier et autoriser la requête. Comprendre comment décoder et inspecter les JWT en toute sécurité est essentiel pour les développeurs qui doivent déboguer les flux d'authentification, vérifier les revendications et résoudre les problèmes liés aux jetons sans exposer de données sensibles ni introduire de vulnérabilités.
Comprendre la Structure JWT
Un JWT se compose de trois parties séparées par des points, chacune encodée en utilisant Base64URL (une variante sûre pour les URL de l'encodage Base64) :
header.payload.signature
Exemple :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwi
bmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4
fwpMeJf36POk6yJV_adQssw5c
Le premier segment est l'en-tête, qui contient des métadonnées sur le jeton, notamment l'algorithme de signature et le type de jeton. Le deuxième segment est la charge utile, qui contient les revendications ou assertions faites. Le troisième segment est la signature, qui est calculée en prenant l'en-tête et la charge utile encodés, en les combinant avec un point, et en signant le résultat en utilisant l'algorithme spécifié dans l'en-tête ainsi qu'une clé secrète ou une clé privée.
Détail de l'Encodage Base64URL
JWT utilise l'encodage Base64URL, qui diffère du Base64 standard de deux manières importantes. Premièrement, il remplace le caractère + par - pour éviter les problèmes dans les paramètres de requête URL où + est interprété comme un espace. Deuxièmement, il remplace le caractère / par _ pour éviter les conflits avec les séparateurs de chemin URL. De plus, les caractères de rembourrage (=) sont supprimés de la fin des segments JWT car ils ne sont pas nécessaires au décodage et ajouteraient seulement des octets inutiles au jeton.
Revendications de l'En-tête JWT
L'en-tête contient généralement l'algorithme et le type de jeton, mais peut également inclure des métadonnées supplémentaires pour des scénarios avancés.
| Revendication | Nom | Exemples de Valeurs |
|---|---|---|
| alg | Algorithme | HS256, RS256, ES256 |
| typ | Type de jeton | JWT |
| kid | ID de clé | id-clé-unique |
| jku | URL du jeu de clés JWK | https://example.com/.well-known/jwks.json |
La revendication kid (ID de clé) est particulièrement importante dans les environnements où plusieurs clés de signature sont en rotation. Elle permet au serveur de trouver la bonne clé à partir d'un ensemble de clés sans deviner. La revendication jku pointe vers une URL contenant un jeu de clés web JSON (JWKS), que le vérificateur peut récupérer pour obtenir la clé publique pour la vérification de la signature. Cependant, récupérer des clés à partir d'URL spécifiées dans des jetons non fiables introduit des risques de sécurité et ne devrait être fait qu'avec une validation appropriée.
Revendications Courantes de la Charge Utile JWT
La charge utile contient des revendications qui fournissent des informations sur l'utilisateur et le jeton lui-même. Ces revendications sont classées comme revendications enregistrées, publiques et privées.
| Revendication | Nom | Description |
|---|---|---|
| sub | Sujet | Identifiant de l'utilisateur |
| iss | Émetteur | Qui a émis le jeton |
| aud | Audience | Destinataire prévu |
| exp | Expiration | Timestamp d'expiration du jeton |
| nbf | Pas avant | Jeton valide à partir de |
| iat | Émis à | Moment de création du jeton |
| jti | ID JWT | Identifiant unique du jeton |
Les revendications enregistrées sont standardisées par la spécification JWT et ont des significations spécifiques. La revendication exp est particulièrement critique pour la sécurité. Si un jeton n'inclut pas de date d'expiration, ou si le serveur omet de la valider, le jeton reste valide indéfiniment, créant une vulnérabilité sérieuse si le jeton est jamais divulgué. La revendication iat enregistre quand le jeton a été créé et peut être utilisée pour déterminer l'âge du jeton ou pour mettre en œuvre des politiques de rotation de jetons.
Au-delà de ces revendications enregistrées, les applications incluent généralement des revendications personnalisées pour les rôles utilisateur, les permissions, les adresses email et d'autres données spécifiques à l'application. Par exemple, un jeton pourrait inclure "role": "admin" ou "permissions": ["read", "write", "delete"]. Ces revendications personnalisées doivent être inspectées attentivement, mais jamais fiées sans vérification de la signature.
Décodage vs Vérification
La distinction entre décodage et vérification est le concept le plus important à comprendre lorsque l'on travaille avec des JWT. Confondre les deux est une source courante de vulnérabilités de sécurité.
Le décodage décode simplement en Base64URL l'en-tête et la charge utile. N'importe qui peut décoder un JWT sans aucune clé secrète. Vous pouvez décoder n'importe quel JWT en le collant dans un outil de décodage ou en exécutant une simple fonction de décodage Base64URL sur les deux premiers segments. Cela rend les JWT utiles pour l'inspection et le débogage, mais cela signifie également que le contenu de la charge utile est lisible publiquement.
La vérification contrôle la signature en utilisant une clé secrète (HMAC) ou une clé publique (RSA/ECDSA). Cela confirme que le jeton n'a pas été falsifié et qu'il a été émis par une partie de confiance. La vérification est l'étape qui fournit la sécurité. Sans elle, un attaquant pourrait modifier la charge utile pour changer l'ID utilisateur ou le rôle, ré-encoder l'en-tête et la charge utile, et créer une nouvelle signature qui semblerait valide pour un serveur qui saute ou implémente incorrectement la vérification de la signature.
Attaques par Confusion d'Algorithme
L'une des vulnérabilités JWT les plus dangereuses est l'attaque par confusion d'algorithme, également connue sous le nom d'attaque par confusion de clé. Cette attaque exploite le fait que l'algorithme est spécifié dans l'en-tête, qui est contrôlé par l'attaquant puisque l'en-tête est seulement encodé en Base64URL et non chiffré.
Dans un scénario typique, un serveur utilise RS256 (RSA avec SHA-256) pour signer les jetons. La clé publique du serveur est largement disponible, tandis que la clé privée est gardée secrète. Un attaquant intercepte un jeton valide et change l'en-tête alg de RS256 à HS256. Puisque HS256 utilise une clé secrète symétrique, le serveur essaiera de vérifier la signature en utilisant l'algorithme HS256. L'erreur critique est si le serveur utilise la clé publique RS256 comme secret HS256. Puisque la clé publique est publique, l'attaquant peut calculer une signature HS256 valide pour n'importe quelle charge utile en utilisant la clé publique comme secret HMAC. Le serveur vérifie ensuite la signature en utilisant la même clé publique et accepte le jeton falsifié.
Pour prévenir cette attaque, les serveurs doivent soit valider que l'algorithme correspond à l'algorithme attendu avant la vérification, soit utiliser une bibliothèque qui est immunisée contre cette attaque par sa conception. De nombreuses bibliothèques JWT modernes utilisent désormais des méthodes séparées pour la vérification asymétrique et symétrique pour éliminer ce risque.
Étapes de Décodage Sécurisé
Suivez ces étapes chaque fois que vous devez décoder et inspecter un jeton JWT.
-
Copiez le jeton JWT depuis votre application. Assurez-vous d'avoir capturé le jeton complet incluant les trois segments séparés par des points. Un jeton partiel ne pourra pas être décodé correctement.
-
Utilisez un outil de décodage fiable comme le Décodeur JWT qui s'exécute entièrement dans le navigateur ou sur votre machine locale. Évitez de coller des jetons dans des outils en ligne inconnus qui pourraient journaliser ou transmettre vos jetons à des serveurs distants. Même si un jeton est de courte durée, l'exposer à un tiers pourrait lui permettre de le réutiliser dans sa fenêtre de validité.
-
Inspectez l'en-tête pour l'algorithme. Confirmez que l'algorithme correspond à ce que vous attendez de votre serveur. Si vous voyez un algorithme inattendu comme
noneou un passage deRS256àHS256, traitez le jeton comme suspect. -
Examinez les revendications de la charge utile. Vérifiez les timestamps
exp(expiration) etnbf(pas avant) pour confirmer que le jeton est actuellement valide. Vérifiez queiss(émetteur) correspond à l'identifiant de votre serveur. Contrôlez que la revendicationaud(audience), si présente, inclut votre application. -
Ne partagez jamais le jeton publiquement. Les jetons postés dans des rapports de bugs, des questions Stack Overflow ou des Gists GitHub peuvent être abusés par quiconque les trouve avant leur expiration. Toujours masquer ou remplacer les jetons par des exemples factices avant de les partager.
-
Ne vérifiez jamais avec des clés non fiables. Si votre code de vérification accepte des clés provenant d'entrées utilisateur, d'URL distantes ou de fichiers de configuration qui peuvent être modifiés par des attaquants, votre système d'authentification entier est compromis.
Décodage des JWT en Code
Bien que les outils en ligne soient pratiques pour une inspection ponctuelle, vous aurez souvent besoin de décoder les JWT par programmation dans votre application. Voici comment décoder une charge utile JWT dans quelques langages de programmation populaires sans effectuer de vérification de signature.
JavaScript (Node.js)
function decodeJWT(token) {
const payload = token.split('.')[1];
const decoded = Buffer.from(payload, 'base64url').toString('utf8');
return JSON.parse(decoded);
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
console.log(decodeJWT(token));
Python
import base64
import json
def decode_jwt(token):
payload = token.split('.')[1]
# Add padding for base64 decoding
padding = 4 - len(payload) % 4
if padding != 4:
payload += '=' * padding
decoded = base64.urlsafe_b64decode(payload)
return json.loads(decoded)
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
print(decode_jwt(token))
Ces exemples montrent le décodage à des fins d'inspection uniquement. Pour la vérification en production, utilisez toujours une bibliothèque JWT bien auditée qui gère correctement la validation des algorithmes, la gestion des clés et la vérification des signatures.
Meilleures Pratiques de Sécurité
Au-delà du décodage sécurisé, suivre ces meilleures pratiques maintiendra votre implémentation JWT sécurisée.
Utilisez des durées d'expiration courtes. Les jetons d'accès devraient expirer dans les 15 à 60 minutes. Les jetons de courte durée limitent les dégâts si un jeton est divulgué. Combinez les jetons d'accès avec des jetons d'actualisation qui ont des durées de vie plus longues et peuvent être révoqués individuellement.
Validez toutes les revendications requises. Vérifiez toujours les revendications exp, nbf, iss et aud lors de la vérification. De nombreuses attaques exploitent une validation de revendication manquante ou incorrecte.
Utilisez des algorithmes asymétriques pour les systèmes distribués. RS256 ou ES256 permettent au serveur de signer les jetons avec une clé privée tandis que n'importe quel service peut les vérifier avec la clé publique. Cela élimine le besoin de partager des secrets entre services.
Implémentez une liste noire de jetons. Pour les scénarios où vous devez révoquer des jetons avant leur expiration, maintenez une liste noire ou utilisez un numéro de version de jeton stocké dans votre base de données. Incrémentez la version lors des changements de mot de passe ou de la suspension de compte pour invalider tous les jetons existants.
Avertissement de Sécurité
N'acceptez jamais les jetons provenant de sources non fiables. Vérifiez toujours la signature sur votre serveur avant de faire confiance à la charge utile. Soyez conscient des attaques par confusion d'algorithme où un attaquant change RS256 en HS256 pour utiliser la clé publique comme secret. Utilisez des bibliothèques JWT réputées qui implémentent la vérification de signature de manière sécurisée, et maintenez-les à jour pour bénéficier des derniers correctifs de sécurité.
Rappelez-vous que décoder n'est pas vérifier. N'importe qui peut lire le contenu d'un JWT en décodant simplement la charge utile en Base64. Traitez les charges utiles JWT comme des informations publiques en ce qui concerne la confidentialité, et fiez-vous à la vérification de la signature pour l'intégrité et l'authenticité.