Quand je reçois un JWT et que ma première question est simplement « qu'est-ce qu'il contient ? », je ne commence pas par chercher un secret de signature. Pour un JWT/JWS signé classique à trois segments, le header et le payload sont encodés en Base64URL, pas chiffrés. Je peux donc les lire sans secret.
La distinction que je garde pendant tout le diagnostic est simple : decode me dit ce que le token affirme ; verify me dit si une clé que je considère déjà comme fiable a signé ces octets ; validate me dit si mon application doit accepter le token.
Pour une première inspection, j'utilise le JWT Decoder.
Je commence par distinguer JWS à 3 segments et JWE à 5 segments
Un JWT signé ressemble généralement à :
header.payload.signature
Un JWE compact comporte normalement cinq segments. Si j'en vois cinq, je n'attends plus de Base64URL qu'il révèle les claims : je suis face à du chiffrement, et le déchiffrement est une opération différente du décodage.
Cette vérification évite de supposer que toute chaîne commençant par eyJ est un payload JWT directement lisible.
Pourquoi le secret n'est pas nécessaire pour lire un JWT signé
Le header et le payload sont du JSON représenté en Base64URL. Base64URL est un encodage, pas un mécanisme de confidentialité.
Je peux par exemple lire :
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-2026-09"
}
puis :
{
"sub": "user_123",
"iss": "https://auth.example.com/",
"aud": "api://backend",
"scope": "read:user write:profile",
"iat": 1789000000,
"exp": 1789003600
}
La signature protège l'intégrité et l'authenticité ; elle ne rend pas les claims d'un JWT signé confidentiels.
Ce que je regarde juste après le décodage
Je ne lis pas tous les champs avec le même niveau de priorité. Je vérifie généralement :
alg— quel algorithme le token déclare-t-il ?kid— quel identifiant de clé pourra aider à la sélection ?iss— quel issuer le token affirme-t-il avoir ?aud— pour quel destinataire ou quelle API est-il prévu ?exp,nbf,iat— que dit sa fenêtre temporelle ?scope,roles,groups— quelles données d'autorisation sont présentes ?sub— quel sujet est représenté ?
La référence des claims JWT va plus loin. À ce stade, ces valeurs restent des données non fiables.
Pourquoi je ne fais pas confiance à un payload lisible
N'importe qui peut construire du JSON, l'encoder en Base64URL et produire une chaîne qui ressemble à un JWT.
Je peux transformer :
{ "role": "user" }
en :
{ "role": "admin" }
et continuer à décoder le résultat. Ce que je ne devrais pas pouvoir faire sans la credential de signature autorisée, c'est produire une nouvelle signature acceptée par un verifier correctement configuré.
C'est pourquoi « decoded successfully » ne signifie jamais « valid JWT » dans mon workflow.
Quand le secret ou la clé publique devient nécessaire
Je passe du décodage à la vérification lorsque ma question devient :
Le signer de confiance a-t-il réellement signé exactement ces octets ?
Pour HS256, j'ai besoin du shared secret exact. Pour RS256, j'ai généralement besoin de la clé publique de l'issuer, pas de sa private key.
Avec du matériel fiable, j'utilise le JWT Signature Verifier. Si l'issuer publie un JWKS, j'utilise le JWKS Validator pour sélectionner la bonne clé avec kid.
Je sépare toujours decode, verify et validate
Decode
Je lis header, payload, timestamps et présence du segment de signature.
Verify
Je vérifie cryptographiquement la signature avec une clé dont la provenance est déjà fiable.
Validate
Je compare iss, aud, exp, nbf, token type, scopes et rôles avec la policy de l'application.
Cette séparation m'empêche de transformer une lecture Base64 réussie en conclusion de sécurité.
Quand je soupçonne un problème de temps
Les NumericDate JWT comme exp, iat et nbf sont exprimées en secondes. En JavaScript, Date.now() utilise des millisecondes.
Pour une lecture rapide, j'utilise le JWT Expiry Checker. Si exp est encore dans le futur mais que l'API renvoie 401, je continue avec issuer, audience et signature.
Comment je traite les bearer tokens de production
Même lisible, un access token reste une credential.
Mes règles :
- je ne mets pas de JWT dans les URLs ;
- je ne logge pas les bearer tokens complets ;
- je préfère un token synthétique ou redacted pour reproduire un bug ;
- je ne colle pas de refresh token, private key ou secret HMAC dans un rapport ;
- je n'envoie pas un token à un serveur uniquement pour faire du Base64URL decode.
JWTDecoder.tools effectue le décodage dans la mémoire du navigateur, mais aucun site ne peut neutraliser un malware ou une extension déjà présente sur l'appareil.
Ma checklist finale
- Identifier JWS 3 segments ou JWE 5 segments.
- Lire header/payload en Base64URL + UTF-8 JSON.
- Examiner
alg,kid,iss,audet les claims temporels. - Considérer les claims comme non fiables avant verification.
- Utiliser JWT Expiry Checker pour les problèmes de temps.
- Utiliser JWT Signature Verifier ou JWKS Validator pour l'authenticité.
- Comparer issuer/audience dans JWT Debugger si l'API refuse toujours le token.
La règle que j'applique est : je n'ai pas besoin d'un secret pour lire un JWT signé normal, mais j'ai besoin d'un chemin de confiance avant de croire ses claims.