Guides JWT

Comment je décode un JWT en JavaScript sans confondre lecture et confiance

Mon flux JavaScript et Node.js pour nettoyer Bearer, traiter Base64URL et UTF-8, parser le JSON et lire exp tout en gardant la signature séparée.

Par JWTDecoder.toolsDernière vérification le 2026-09-13

Voix éditoriale : la première personne décrit le workflow documenté de JWTDecoder.tools ; JWTDecoder.tools est l’éditeur et l’auteur organisationnel.

JWT → Base64URL → UTF-8 → JSON
JWT → Base64URL → UTF-8 → JSON · decode ≠ verify

Quand je dois décoder un JWT en JavaScript, je fixe une règle avant d'écrire le code : le décodage sert à inspecter, pas à authentifier. Un JSON.parse() réussi ne prouve ni l'authenticité ni l'acceptabilité du token.

Pour une inspection rapide, j'utilise le JWT Decoder. Dans une application, je suis six étapes explicites.

Les six étapes que j'utilise

  1. Retirer Bearer ou Authorization: Bearer.
  2. Vérifier le nombre de segments.
  3. Convertir Base64URL en bytes correctement.
  4. Décoder les bytes en UTF-8.
  5. Parser JSON avec des erreurs distinctes.
  6. Inspecter exp et les claims, tout en séparant la signature.

1. Normaliser l'entrée

function normalizeJwtInput(input) {
  return input
    .trim()
    .replace(/^authorization\s*:\s*bearer\s+/i, "")
    .replace(/^bearer\s+/i, "")
    .trim()
    .replace(/^['"]|['"]$/g, "");
}

Je veux que le reste du decoder travaille avec une seule représentation : le compact token.

2. Compter les segments avant de décoder

Un JWS signé est généralement :

header.payload.signature

Un JWE compact est normalement composé de cinq segments.

function splitSignedJwt(input) {
  const token = normalizeJwtInput(input);
  const parts = token.split(".");

  if (parts.length === 5) throw new Error("Ce token ressemble à un JWE chiffré");
  if (parts.length !== 3) throw new Error(`3 segments attendus, ${parts.length} reçus`);
  return parts;
}

Je préfère cela à lire aveuglément token.split('.')[1].

3. Traiter Base64URL comme Base64URL

Le JWT utilise l'alphabet URL-safe et omet souvent le padding.

function base64UrlToBytes(segment) {
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(
    base64.length + ((4 - (base64.length % 4)) % 4),
    "="
  );
  const binary = atob(padded);
  return Uint8Array.from(binary, c => c.charCodeAt(0));
}

atob() ne résout pas le problème UTF-8 à lui seul.

4. Décoder UTF-8 avant JSON

Les payloads ASCII peuvent masquer longtemps une mauvaise implémentation. Dès qu'un nom ou une valeur localisée contient des caractères non ASCII, les raccourcis cassent.

function decodeJwtJson(segment, label) {
  let text;
  try {
    const bytes = base64UrlToBytes(segment);
    text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
  } catch {
    throw new Error(`${label} n'est pas du Base64URL/UTF-8 valide`);
  }

  let value;
  try {
    value = JSON.parse(text);
  } catch {
    throw new Error(`${label} ne contient pas un JSON valide`);
  }

  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error(`${label} doit être un objet JSON`);
  }
  return value;
}

Séparer les erreurs d'encodage des erreurs JSON accélère mon diagnostic.

5. Construire un decoder minimal

function decodeJwt(input) {
  const token = normalizeJwtInput(input);
  const parts = token.split(".");
  if (parts.length !== 3) throw new Error("JWT signé à 3 segments attendu");

  return {
    token,
    header: decodeJwtJson(parts[0], "JWT header"),
    payload: decodeJwtJson(parts[1], "JWT payload"),
    signature: parts[2]
  };
}

À ce stade je peux lire alg, kid, iss, aud, exp. Je n'ai toujours pas vérifié la signature.

6. exp : secondes contre millisecondes

function getExpiration(payload) {
  if (typeof payload.exp !== "number") return { hasExpiration: false };
  return {
    hasExpiration: true,
    expiresAt: new Date(payload.exp * 1000),
    expired: Date.now() >= payload.exp * 1000
  };
}

Je n'écris pas :

Date.now() >= payload.exp

Pour une timeline complète, j'utilise JWT Expiry Checker.

Node.js : j'utilise parfois Buffer

function decodeBase64UrlJson(segment) {
  return JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
}

Sur un serveur qui authentifie des requêtes, ce code d'inspection n'est jamais ma seule protection. J'utilise une bibliothèque JOSE/JWT avec signature, issuer, audience et algorithm policy.

jwt-decode reste un decoder

import { jwtDecode } from "jwt-decode";

const payload = jwtDecode(token);
const header = jwtDecode(token, { header: true });

La documentation de la bibliothèque sépare elle aussi decode et validation. Je ne l'utilise pas comme authentication gate côté serveur.

Pourquoi j'évite le one-liner classique

JSON.parse(atob(token.split(".")[1]));

Il :

  • suppose que le payload existe ;
  • traite mal la différence Base64/Base64URL ;
  • masque le padding ;
  • peut casser UTF-8 ;
  • donne de mauvaises erreurs ;
  • ignore alg et kid ;
  • fait oublier que la signature n'est pas vérifiée.

Après decode, il reste verify et validate

Pour l'authenticité, je passe au JWT Signature Verifier ou au JWKS Validator.

Puis je contrôle iss, aud, exp, nbf, token type, scopes et rôles dans JWT Validator ou JWT Debugger.

Mes règles de confidentialité côté navigateur

  • pas de JWT dans l'URL ;
  • pas de bearer token complet dans les logs ;
  • pas de stockage persistant inutile pour le debugging ;
  • tokens synthétiques/redacted privilégiés ;
  • pas d'envoi serveur uniquement pour décoder Base64URL.

Ma checklist JavaScript

  1. Nettoyer Bearer.
  2. Vérifier les segments.
  3. Base64URL → bytes.
  4. Bytes → UTF-8.
  5. Parser header/payload proprement.
  6. Lire alg, kid, iss, aud, temps.
  7. Multiplier NumericDate par 1000 pour JavaScript.
  8. Marquer le résultat decoded, jamais verified.
  9. Vérifier la signature si la confiance compte.
  10. Valider issuer, audience, temps, type et permissions si l'acceptation compte.

Le point d'ingénierie essentiel est de garder séparés readable, cryptographically verified et acceptable par mon application.

Sources et références

Guides associés