JWT Decoder Python — Décoder des JWT avec PyJWT

·DevOps Engineer & Python Automation Specialist·Révisé parMaria Santos·Publié

Utilisez le Décodeur JWT gratuit directement dans votre navigateur — sans installation.

Essayer Décodeur JWT en ligne →

Toute API utilisant l'authentification par token vous remet un JWTà un moment ou un autre, et comprendre ce qu'il contient est une tâche qui revient constamment en développement. Un décodeur JWT en Pythonprend cette chaîne base64 opaque et la transforme en un dictionnaire de claims lisible avec lequel vous pouvez réellement travailler. Le paquet PyPI qu'il vous faut est PyJWT — installé avec pip install PyJWT mais importé via import jwt. Ce guide passe en revue jwt.decode() avec vérification complète de la signature, le décodage sans secret pour une inspection rapide, le décodage manuel en base64 sans bibliothèque, la vérification de clé publique RS256, et les pièges courants rencontrés dans des systèmes d'authentification en production. Pour une vérification ponctuelle, le décodeur JWT en ligne fait cela instantanément sans aucun code. Tous les exemples ciblent Python 3.10+ et PyJWT 2.x.

  • pip install PyJWT, puis import jwt — le nom du paquet et le nom d'import sont différents, ce qui piège presque tout le monde.
  • jwt.decode(token, key, algorithms=["HS256"]) retourne un dict simple avec les claims. Passez toujours algorithms explicitement.
  • Pour inspecter les claims sans vérification : jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
  • Pour les tokens RSA/EC : pip install PyJWT cryptography — le backend cryptography est requis pour les algorithmes asymétriques.
  • Le décodage manuel (base64 + json) fonctionne sans bibliothèque mais ignore toute validation de signature et d'expiration.

Qu'est-ce que le décodage JWT ?

Un JSON Web Token est composé de trois segments encodés en base64url séparés par des points : un en-tête (algorithme et type de token), un payload (les claims — identifiant utilisateur, rôles, date d'expiration), et une signature. Décoder un JWT signifie extraire les segments d'en-tête et de payload, les décoder en base64url, et parser le JSON résultant en un dictionnaire de claims.

L'en-tête indique quel algorithme a été utilisé pour signer le token et inclut parfois un kid (identifiant de clé) pour trouver la bonne clé de vérification. Le payload contient les données réelles : à qui le token a été émis (sub), quand il expire (exp), pour quel service il est destiné (aud), ainsi que tout claim personnalisé défini par votre application. Le segment de signature prouve que le token n'a pas été altéré, mais vous avez besoin de la clé secrète ou de la clé publique pour le vérifier. Décodage et vérification sont deux opérations distinctes. Vous pouvez décoder le payload sans vérifier la signature (utile pour le débogage), mais ne faites jamais confiance à des claims non vérifiés pour des décisions d'autorisation.

Before · json
After · json
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
  "sub": "usr_8f2a",
  "role": "admin",
  "exp": 1711815600
}

jwt.decode() — Décoder et vérifier avec PyJWT

jwt.decode() est la fonction principale de la bibliothèque PyJWT. Elle prend la chaîne du token encodé, la clé secrète (pour les algorithmes HMAC) ou la clé publique (pour RSA/EC), et une liste algorithms obligatoire. La fonction vérifie la signature, contrôle les claims standard comme exp et nbf, et retourne le payload sous forme de dictionnaire Python. En cas d'échec — mauvaise signature, token expiré, mauvais algorithme — elle lève une exception spécifique.

Exemple minimal fonctionnel

Python 3.10+
import jwt

# A shared secret between the issuer and this service
SECRET_KEY = "k8s-webhook-signing-secret-2026"

# Encode a token first (simulating what an auth server would issue)
token = jwt.encode(
    {"sub": "usr_8f2a", "role": "admin", "team": "platform"},
    SECRET_KEY,
    algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3Jf...

# Decode and verify the token
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin', 'team': 'platform'}
print(payload["role"])
# admin

Le paramètre algorithms est une liste, pas une simple chaîne, et il est obligatoire dans PyJWT 2.x. C'est une mesure de sécurité : sans elle, un attaquant pourrait forger un token avec alg: none dans l'en-tête et contourner entièrement la vérification. Spécifiez toujours exactement les algorithmes que votre application accepte. Si vous n'émettez que des tokens HS256, la liste doit être ["HS256"] — pas ["HS256", "RS256", "none"]. Restreindre la liste réduit la surface d'attaque.

Un point qui m'a perturbé au début : PyJWT 2.x a modifié jwt.encode() pour retourner une chaîne au lieu d'octets. Si vous lisez de vieilles réponses Stack Overflow qui appellent .decode("utf-8") sur le token encodé, ce code est de l'ère PyJWT 1.x et lèvera une AttributeError sur la version 2.x. Le token est déjà une chaîne — utilisez-le directement.

Cycle complet avec expiration

Python 3.10+ — encode then decode with exp
import jwt
from datetime import datetime, timedelta, timezone

SECRET_KEY = "webhook-processor-secret"

# Create a token that expires in 1 hour
payload = {
    "sub": "svc_payment_processor",
    "iss": "auth.internal.example.com",
    "aud": "https://api.example.com",
    "exp": datetime.now(timezone.utc) + timedelta(hours=1),
    "permissions": ["orders:read", "refunds:create"],
}

token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")

# Later, when the token arrives in a request header:
try:
    decoded = jwt.decode(
        token,
        SECRET_KEY,
        algorithms=["HS256"],
        audience="https://api.example.com",
        issuer="auth.internal.example.com",
    )
    print(f"Service: {decoded['sub']}")
    print(f"Permissions: {decoded['permissions']}")
except jwt.ExpiredSignatureError:
    print("Token expired — request re-authentication")
except jwt.InvalidAudienceError:
    print("Token not intended for this API")
except jwt.InvalidIssuerError:
    print("Token issued by unknown authority")
Note :PyJWT convertit automatiquement les objets datetimeen timestamps Unix lors de l'encodage. Au décodage, les claims exp, iat, et nbfsont retournés sous forme d'entiers, pas d'objets datetime. Vous devez les convertir vous-même avec datetime.fromtimestamp(payload["exp"], tz=timezone.utc).

Décoder un JWT sans vérification de signature

Parfois vous devez lire les claims avant de pouvoir vérifier le token. Un scénario courant : l'en-tête du token contient un champ kid (identifiant de clé), et vous devez récupérer la clé publique correspondante depuis un endpoint JWKS avant de pouvoir vérifier. PyJWT prend en charge cela avec l'option verify_signature: False. Vous passez quand même la liste algorithms, mais l'argument key est ignoré.

Python 3.10+ — unverified decode
import jwt

token = (
    "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZy0xNzI2In0"
    ".eyJzdWIiOiJ1c3JfM2M3ZiIsInNjb3BlIjoicmVhZDpvcmRlcnMiLCJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIn0"
    ".signature_placeholder"
)

# Step 1: Read claims without verification to get routing info
unverified = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["RS256"]
)
print(unverified)
# {'sub': 'usr_3c7f', 'scope': 'read:orders', 'iss': 'auth.example.com'}

# Step 2: Read the header to find which key to use
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'RS256', 'typ': 'JWT', 'kid': 'sig-1726'}
# Now use header['kid'] to fetch the correct public key from your JWKS endpoint
Avertissement :Les tokens non vérifiés ne sont pas fiables. N'utilisez ce modèle que pour des décisions de routage (quelle clé récupérer, quel tenant rechercher). Ne prenez jamais de décisions d'autorisation sur la base de claims non vérifiés. Un attaquant peut mettre n'importe quoi dans le payload.

Il y a une distinction subtile ici. jwt.get_unverified_header() lit uniquement l'en-tête — le premier segment. L'appel jwt.decode() avec verify_signature: False lit le payload (deuxième segment). Combinés, vous pouvez tout extraire d'un token sans clé. PyJWT valide quand même que le token a la bonne structure (trois segments séparés par des points, base64 valide, JSON valide) même lorsque la vérification de signature est désactivée. Si le token est structurellement malformé, il lève DecodeError indépendamment des options que vous passez.

Référence des paramètres jwt.decode()

La signature complète est jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require). Tous les paramètres après algorithms sont des arguments nommés uniquement.

Paramètre
Type
Défaut
Description
jwt
str | bytes
(requis)
La chaîne JWT encodée à décoder
key
str | bytes | dict
(requis)
Clé secrète (HMAC) ou clé publique (RSA/EC) pour la vérification
algorithms
list[str]
(requis)
Algorithmes autorisés — ex. ["HS256"], ["RS256"]. Ne jamais omettre.
options
dict
{}
Indicateurs de vérification personnalisés : verify_signature, verify_exp, verify_aud, etc.
audience
str | list[str]
None
Valeur attendue du claim aud — lève InvalidAudienceError en cas de non-concordance
issuer
str
None
Valeur attendue du claim iss — lève InvalidIssuerError en cas de non-concordance
leeway
timedelta | int
0
Tolérance en secondes pour le décalage d'horloge lors des vérifications exp et nbf
require
list[str]
[]
Claims devant être présents — lève MissingRequiredClaimError sinon

Le dict options offre un contrôle fin sur les validations effectuées par PyJWT. Les clés correspondent à des vérifications individuelles : verify_signature, verify_exp, verify_nbf, verify_iss, verify_aud, et verify_iat. Toutes sont à True par défaut, sauf si vous les définissez explicitement à False. En production, laissez-les toutes à leurs valeurs par défaut. Je ne désactive des vérifications individuelles qu'en développement, quand je travaille avec de vieux tokens de test et que j'ai besoin de contourner temporairement l'expiration.

Python 3.10+ — using options and require
import jwt

# Require specific claims to be present — raises MissingRequiredClaimError if absent
payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"require": ["exp", "iss", "sub"]},
    issuer="auth.internal.example.com",
)

# During development only: skip expiry to test with old tokens
dev_payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"verify_exp": False},  # DO NOT use in production
)

Décodage manuel d'un JWT avec base64 et json

Vous pouvez décoder un payload JWT en n'utilisant que la bibliothèque standard Python — sans pip install nécessaire. C'est réellement utile dans plusieurs situations : des scripts de débogage où ajouter une dépendance serait excessif, des environnements CI restreints où seule la bibliothèque standard est disponible, des fonctions AWS Lambda où vous souhaitez minimiser le temps de démarrage à froid, ou simplement pour comprendre ce qu'est réellement un JWT. Le processus est simple : découpez sur les points, prenez le segment voulu, ajoutez le padding base64, décodez et parsez le JSON.

Les modules base64 et json font tous deux partie de la bibliothèque standard Python, donc cette approche fonctionne sur toute installation Python à partir de la 3.6. Les fonctions ci-dessous traitent séparément l'en-tête (premier segment) et le payload (deuxième segment) :

Python 3.10+ — manual JWT decode without any library
import base64
import json

def decode_jwt_payload(token: str) -> dict:
    """Decode the JWT payload without signature verification.
    Works with any JWT — HS256, RS256, ES256, etc.
    """
    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError(f"Expected 3 JWT segments, got {len(parts)}")

    payload_b64 = parts[1]
    # base64url uses - and _ instead of + and /
    # Python's urlsafe_b64decode handles this, but needs padding
    payload_b64 += "=" * (-len(payload_b64) % 4)
    payload_bytes = base64.urlsafe_b64decode(payload_b64)
    return json.loads(payload_bytes)


def decode_jwt_header(token: str) -> dict:
    """Decode the JWT header (algorithm, key ID, type)."""
    header_b64 = token.split(".")[0]
    header_b64 += "=" * (-len(header_b64) % 4)
    return json.loads(base64.urlsafe_b64decode(header_b64))


# Example usage
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

header = decode_jwt_header(token)
print(f"Algorithm: {header['alg']}")
# Algorithm: HS256

claims = decode_jwt_payload(token)
print(f"Subject: {claims['sub']}")
print(f"Role: {claims['role']}")
# Subject: usr_8f2a
# Role: admin

L'astuce du padding (+= "=" * (-len(s) % 4)) est ce que tout le monde oublie. Le base64url des JWT omet les caractères = de fin, mais la fonction urlsafe_b64decode de Python les requiert. Sans la correction du padding, vous obtenez une binascii.Error: Incorrect padding.

Note :Le décodage manuel ne vérifie pas la signature. N'importe qui peut modifier le payload d'un JWT et le ré-encoder. N'utilisez cette approche que pour l'inspection et le débogage, jamais pour la logique d'autorisation. Pour tout ce qui est important, utilisez jwt.decode() avec une vraie clé.

Décoder des JWTs depuis des réponses API et des fichiers

Les deux scénarios réels les plus courants : extraire un JWT d'une réponse HTTP (un endpoint de token OAuth, une API de connexion) et lire des tokens depuis des fichiers (identifiants de compte de service, secrets montés dans Kubernetes, tokens mis en cache sur disque). Les deux nécessitent une gestion des erreurs appropriée. Les requêtes réseau échouent. Les fichiers disparaissent. Les tokens expirent entre le moment où ils ont été mis en cache et celui où ils sont lus.

Les exemples ci-dessous utilisent httpx pour les appels HTTP (remplacez par requests si vous préférez, le modèle est identique) et pathlib.Path pour les opérations sur les fichiers. Chaque exemple capture des exceptions PyJWT spécifiques plutôt qu'un simple except Exception, afin de répondre de manière appropriée à chaque mode d'échec : ré-authentifier à l'expiration, alerter en cas d'échec de signature, réessayer en cas de timeout réseau.

Décoder un JWT depuis une réponse API

Python 3.10+ — decode JWT from OAuth token endpoint
import jwt
import httpx  # or requests

TOKEN_ENDPOINT = "https://auth.example.com/oauth/token"
SECRET_KEY = "shared-webhook-signing-key"

def get_and_decode_token() -> dict:
    """Fetch an access token from the auth server and decode it."""
    try:
        response = httpx.post(
            TOKEN_ENDPOINT,
            data={
                "grant_type": "client_credentials",
                "client_id": "svc_order_processor",
                "client_secret": "cs_9f3a7b2e",
            },
            timeout=10.0,
        )
        response.raise_for_status()
    except httpx.HTTPError as exc:
        raise RuntimeError(f"Token request failed: {exc}") from exc

    token_data = response.json()
    access_token = token_data["access_token"]

    try:
        payload = jwt.decode(
            access_token,
            SECRET_KEY,
            algorithms=["HS256"],
            audience="https://api.example.com",
        )
        return payload
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Invalid token from auth server: {exc}") from exc


claims = get_and_decode_token()
print(f"Service: {claims['sub']}, Scopes: {claims.get('scope', 'none')}")

Décoder un JWT depuis un fichier

Python 3.10+ — read and decode a cached token file
import jwt
from pathlib import Path
from datetime import datetime, timezone

TOKEN_PATH = Path("/var/run/secrets/service-account-token")
PUBLIC_KEY_PATH = Path("/etc/ssl/auth/public_key.pem")

def decode_token_from_file() -> dict:
    """Read a JWT from a file, verify with a PEM public key."""
    try:
        token = TOKEN_PATH.read_text().strip()
        public_key = PUBLIC_KEY_PATH.read_text()
    except FileNotFoundError as exc:
        raise RuntimeError(f"Missing file: {exc.filename}") from exc

    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],
            audience="https://internal-api.example.com",
        )
    except jwt.ExpiredSignatureError:
        exp_time = jwt.decode(
            token,
            options={"verify_signature": False},
            algorithms=["RS256"],
        ).get("exp", 0)
        expired_at = datetime.fromtimestamp(exp_time, tz=timezone.utc)
        raise RuntimeError(f"Token expired at {expired_at.isoformat()}")
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Token verification failed: {exc}") from exc

    return payload


claims = decode_token_from_file()
print(f"Subject: {claims['sub']}, Issuer: {claims['iss']}")

Décodage JWT en ligne de commande

Parfois vous avez juste besoin d'inspecter un token depuis le terminal sans écrire de script. Peut-être déboguez-vous un flux OAuth et voulez voir ce qui se trouve dans l'en-tête Authorization, ou vous avez récupéré un token dans les DevTools du navigateur et voulez vérifier son expiration. Le flag -c de Python en fait une commande d'une seule ligne. Envoyez le token via pipe et obtenez les claims en JSON formaté. Pas besoin de fichier script, pas besoin d'environnement virtuel.

Bash
# Decode JWT payload from clipboard or variable (no verification)
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
payload = token.split('.')[1]
payload += '=' * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
"
# {
#   "sub": "usr_8f2a",
#   "role": "admin"
# }
Bash
# Decode JWT header to check algorithm and key ID
echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InNpZy0xNzI2In0.payload.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
header = token.split('.')[0]
header += '=' * (-len(header) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(header)), indent=2))
"
# {
#   "alg": "RS256",
#   "kid": "sig-1726"
# }
Bash
# If PyJWT is installed, verify and decode in one step
python3 -c "
import jwt, sys, json
token = sys.argv[1]
payload = jwt.decode(token, options={'verify_signature': False}, algorithms=['HS256'])
print(json.dumps(payload, indent=2))
" "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.sig"

Pour une alternative visuelle sans configuration de terminal, collez votre token dans le décodeur JWT ToolDecket visualisez instantanément l'en-tête, le payload et le statut de vérification de la signature.

python-jose et autres alternatives

python-jose est une bibliothèque JWT alternative qui prend en charge nativement JWS, JWE (tokens chiffrés) et JWK. Si votre application doit gérer des JWTs chiffrés (JWE) — où le payload lui-même est chiffré, pas seulement signé — python-jose est le bon choix car PyJWT ne prend pas en charge JWE du tout. La bibliothèque dispose également d'une gestion native des ensembles de clés JWKS, ce qui simplifie l'intégration avec des fournisseurs d'identité comme Auth0, Okta ou Keycloak qui exposent des ensembles de clés rotatifs. L'interface de décodage est presque identique à PyJWT, donc passer de l'un à l'autre nécessite des changements de code minimaux :

Python 3.10+ — python-jose
# pip install python-jose[cryptography]
from jose import jwt as jose_jwt

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInNjb3BlIjoib3JkZXJzOnJlYWQifQ.signature"

# Verified decode — same pattern as PyJWT
payload = jose_jwt.decode(
    token,
    "signing-secret-key",
    algorithms=["HS256"],
    audience="https://api.example.com",
)
print(payload)
# {'sub': 'usr_8f2a', 'scope': 'orders:read'}

# Unverified decode
claims = jose_jwt.get_unverified_claims(token)
header = jose_jwt.get_unverified_header(token)
print(f"Algorithm: {header['alg']}, Subject: {claims['sub']}")

Ma recommandation : commencez par PyJWT. Il couvre 95 % des cas d'usage JWT, dispose de la plus grande communauté, et l'API est propre. Passez à python-jose si vous avez besoin du support JWE ou préférez sa gestion JWKS. Une troisième option qui mérite d'être mentionnée est Authlib, qui intègre la gestion JWT au sein d'un framework OAuth/OIDC beaucoup plus large. Si vous utilisez déjà Authlib pour des flux client OAuth, son module authlib.jose.jwt vous évite d'ajouter une deuxième dépendance JWT. Sinon, c'est une dépendance lourde juste pour le décodage de tokens.

Sortie terminal avec coloration syntaxique

Lire des claims JWT bruts dans un terminal est suffisant pour des vérifications rapides, mais quand vous déboguez des payloads de tokens régulièrement (je le faisais quotidiennement en construisant une passerelle d'authentification interne), une sortie colorisée fait vraiment la différence. Les valeurs de type chaîne, nombre, booléen et null s'affichent en couleurs distinctes, ce qui vous permet de repérer en un coup d'œil une permission manquante ou un timestamp d'expiration incorrect sans lire chaque caractère.

La bibliothèque rich (pip install rich) dispose d'une fonction print_json qui prend soit une chaîne JSON soit un dict Python et l'affiche avec une coloration syntaxique complète dans le terminal. Combinez-la avec PyJWT pour un workflow d'inspection JWT en deux lignes :

Python 3.10+ — rich terminal output
# pip install rich PyJWT
import jwt
from rich import print_json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbIm9yZGVyczpyZWFkIiwicmVmdW5kczpjcmVhdGUiXSwiZXhwIjoxNzExODE1NjAwfQ.sig"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)

# Colorized, indented JSON output in the terminal
print_json(data=payload)
# {
#   "sub": "usr_8f2a",           ← strings in green
#   "role": "admin",
#   "permissions": [
#     "orders:read",
#     "refunds:create"
#   ],
#   "exp": 1711815600            ← numbers in cyan
# }
Note :La sortie de richcontient des codes d'échappement ANSI. Ne l'écrivez pas dans des fichiers ni ne la retournez depuis des endpoints API — elle est réservée à l'affichage terminal. Utilisez json.dumps()quand vous avez besoin d'une sortie en texte brut.

Traitement de grands volumes de tokens

Les tokens JWT eux-mêmes sont petits (généralement moins de 2 Ko chacun), mais il existe des scénarios où vous les traitez en masse. Analyse des journaux d'audit après un incident de sécurité. Scripts de migration de sessions lors d'un changement de fournisseur d'authentification. Validation par lot pour la conformité où vous devez prouver que chaque token émis au cours des 90 derniers jours a été signé avec la bonne clé. Si vous avez des dizaines de milliers de tokens dans un fichier journal NDJSON, le traitement ligne par ligne évite de charger l'intégralité du fichier en mémoire et vous permet de rapporter les résultats de manière incrémentale.

Valider des tokens par lot depuis un journal d'audit

Python 3.10+ — streaming token validation
import jwt
import json
from pathlib import Path

SECRET_KEY = "audit-log-signing-key"

def validate_token_log(log_path: str) -> dict:
    """Process an NDJSON file where each line has a 'token' field.
    Returns counts of valid, expired, and invalid tokens.
    """
    stats = {"valid": 0, "expired": 0, "invalid": 0}

    with open(log_path) as fh:
        for line_num, line in enumerate(fh, 1):
            line = line.strip()
            if not line:
                continue

            try:
                record = json.loads(line)
                token = record["token"]
            except (json.JSONDecodeError, KeyError):
                stats["invalid"] += 1
                continue

            try:
                jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
                stats["valid"] += 1
            except jwt.ExpiredSignatureError:
                stats["expired"] += 1
            except jwt.InvalidTokenError:
                stats["invalid"] += 1

    return stats


result = validate_token_log("auth-events-2026-03.ndjson")
print(f"Valid: {result['valid']}, Expired: {result['expired']}, Invalid: {result['invalid']}")
# Valid: 14832, Expired: 291, Invalid: 17

Extraire des claims depuis un export NDJSON de tokens

Python 3.10+ — extract and transform claims from token log
import base64
import json
from datetime import datetime, timezone

def extract_claims_stream(input_path: str, output_path: str):
    """Read tokens line by line, decode payloads, write flattened claims."""
    with open(input_path) as infile, open(output_path, "w") as outfile:
        for line in infile:
            line = line.strip()
            if not line:
                continue

            record = json.loads(line)
            token = record.get("access_token", "")
            parts = token.split(".")
            if len(parts) != 3:
                continue

            payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
            claims = json.loads(base64.urlsafe_b64decode(payload_b64))

            # Flatten into an audit-friendly record
            flat = {
                "timestamp": record.get("timestamp"),
                "subject": claims.get("sub"),
                "issuer": claims.get("iss"),
                "expired_at": datetime.fromtimestamp(
                    claims.get("exp", 0), tz=timezone.utc
                ).isoformat(),
            }
            outfile.write(json.dumps(flat) + "\n")

extract_claims_stream("token-audit.ndjson", "claims-extract.ndjson")
Note :Pour les fichiers de quelques centaines de Mo, la lecture ligne par ligne est suffisamment efficace. Si vous atteignez les limites de performance sur de très grands exports de tokens, envisagez d'utiliser multiprocessing.Pool pour répartir la validation sur plusieurs cœurs, chaque token étant indépendant.

Erreurs courantes

Ces quatre erreurs reviennent régulièrement dans les revues de code et les questions Stack Overflow. Chacune est facile à commettre, et le message d'erreur que PyJWT vous donne ne pointe pas toujours directement vers la cause. J'ai vu le seul problème de nommage du paquet faire perdre des heures de débogage à quelqu'un qui installe la mauvaise bibliothèque et obtient une API complètement inattendue.

Confusion entre les noms de paquets PyJWT et python-jwt

Problème : Exécuter pip install jwt ou pip install python-jwt installe un paquet entièrement différent. import jwt échoue ensuite ou vous donne une API que vous ne reconnaissez pas.

Correction : Installez toujours avec pip install PyJWT. L'import est import jwt. Vérifiez avec pip show PyJWT pour confirmer le bon paquet.

Before · Python
After · Python
pip install jwt
# or
pip install python-jwt

import jwt  # wrong package — different API entirely
pip install PyJWT

import jwt  # correct — this is PyJWT
print(jwt.__version__)  # 2.x
Omission du paramètre algorithms

Problème : Dans PyJWT 1.x, algorithms était optionnel et autorisait par défaut n'importe quel algorithme. Cela créait une vulnérabilité de sécurité où un attaquant pouvait définir alg: none. PyJWT 2.x lève désormais DecodeError si algorithms est absent.

Correction : Passez toujours algorithms sous forme de liste explicite. N'utilisez que les algorithmes avec lesquels votre application émet réellement des tokens.

Before · Python
After · Python
# PyJWT 2.x — this raises DecodeError
payload = jwt.decode(token, SECRET_KEY)
# DecodeError: algorithms must be specified
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
Utilisation de jwt.decode() avec le mauvais type de clé pour RS256

Problème : Passer une chaîne secrète à jwt.decode() avec algorithms=["RS256"] lève InvalidSignatureError. RS256 nécessite une clé publique encodée en PEM, pas une chaîne secrète partagée.

Correction : Chargez la clé publique PEM depuis un fichier ou une variable d'environnement. Installez le paquet cryptography : pip install PyJWT cryptography.

Before · Python
After · Python
# This fails — RS256 needs a public key, not a string secret
payload = jwt.decode(token, "my-secret", algorithms=["RS256"])
# InvalidSignatureError
public_key = open("public_key.pem").read()
payload = jwt.decode(token, public_key, algorithms=["RS256"])
Oubli du padding base64 lors du décodage manuel

Problème : L'encodage base64url des JWT supprime les caractères = de fin. base64.urlsafe_b64decode de Python lève binascii.Error: Incorrect padding si vous passez le segment brut sans corriger le padding.

Correction : Ajoutez le padding avant le décodage : segment += '=' * (-len(segment) % 4). Cette formule produit toujours le bon nombre de caractères de padding (0, 1, 2 ou 3).

Before · Python
After · Python
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect padding
import base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64)  # works

PyJWT vs alternatives — Comparaison rapide

Méthode
Vérifie la signature
Valide les claims
Types personnalisés
Installation requise
PyJWT jwt.decode()
✓ (exp, aud, iss, nbf)
N/A (retourne un dict)
pip install PyJWT
PyJWT décodage non vérifié
N/A
pip install PyJWT
Décodage manuel base64
N/A
Non (stdlib)
python-jose jwt.decode()
N/A
pip install python-jose
Authlib jwt.decode()
N/A
pip install Authlib
PyJWT + cryptography
✓ (RSA/EC)
N/A
pip install PyJWT cryptography

PyJWT est le bon point de départ pour la plupart des applications Python. Il couvre HMAC et (avec le backend cryptography) la vérification de signatures RSA et EC. Si vous avez besoin de JWE (tokens chiffrés), passez à python-jose ou Authlib. Le décodage manuel en base64 fonctionne pour le débogage mais n'offre aucune garantie de sécurité.

Voici quand je choisis chaque option : PyJWT pour tout service web standard utilisant HS256 ou RS256. python-jose quand l'architecture inclut des tokens chiffrés ou une rotation JWKS. Le base64 manuel pour une inspection rapide dans des environnements où pip n'est pas disponible (conteneurs CI, hôtes de production restreints, démarrages à froid AWS Lambda où vous souhaitez minimiser les dépendances). Authlib quand le projet l'utilise déjà pour des flux client OAuth et qu'ajouter une autre bibliothèque JWT serait redondant.

Pour une alternative sans code, collez n'importe quel token dans le décodeur JWTpour voir l'en-tête et le payload décodés avec des retours sur la validation des claims.

Questions fréquentes

Comment décoder un JWT en Python sans vérifier la signature ?

Passez options={"verify_signature": False} et algorithms=["HS256"] à jwt.decode(). Cela retourne le dict du payload sans vérifier la signature. Utilisez cette approche uniquement pour l'inspection — lire les claims avant de récupérer la bonne clé publique, ou déboguer en développement. Ne sautez jamais la vérification sur des tokens qui contrôlent l'accès à quoi que ce soit.

Python
import jwt

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}

Quelle est la différence entre PyJWT et python-jwt ?

PyJWT (pip install PyJWT, import jwt) est la bibliothèque JWT la plus populaire pour Python, avec plus de 80 millions de téléchargements mensuels. python-jwt (pip install python_jwt) est une bibliothèque distincte, bien moins utilisée, avec une API différente. Si vous voyez import jwt dans le code de quelqu'un, il utilise PyJWT. La confusion vient du fait que le nom du paquet PyPI (PyJWT) diffère du nom d'import (jwt). Restez sur PyJWT sauf raison spécifique.

Comment décoder un JWT avec RS256 en Python ?

Installez PyJWT et le backend cryptography : pip install PyJWT cryptography. Passez ensuite la clé publique encodée en PEM comme argument key et algorithms=["RS256"]. PyJWT délègue la vérification de la signature RSA à la bibliothèque cryptography. Sans le paquet cryptography installé, PyJWT lève une erreur lorsque vous tentez d'utiliser des algorithmes RSA ou EC.

Python
import jwt

public_key = open("public_key.pem").read()

payload = jwt.decode(
    token,
    public_key,
    algorithms=["RS256"],
    audience="https://api.example.com"
)

Pourquoi PyJWT lève-t-il ExpiredSignatureError ?

PyJWT vérifie le claim exp (expiration) par défaut. Si l'heure UTC actuelle dépasse le timestamp exp, il lève jwt.ExpiredSignatureError. Vous pouvez ajouter une tolérance pour le décalage d'horloge avec le paramètre leeway : jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Cela accorde une période de grâce de 30 secondes. Pour désactiver entièrement la vérification d'expiration (non recommandé en production), passez options={"verify_exp": False}.

Python
import jwt
from datetime import timedelta

try:
    payload = jwt.decode(token, secret, algorithms=["HS256"], leeway=timedelta(seconds=30))
except jwt.ExpiredSignatureError:
    print("Token has expired — prompt re-authentication")

Peut-on lire les claims JWT sans aucune bibliothèque en Python ?

Oui. Découpez le token sur les points, prenez le deuxième segment (le payload), complétez-le avec des caractères = pour que la longueur soit un multiple de 4, puis décodez-le en base64url et parsez le JSON. Cela vous donne le dict des claims mais ne vérifie pas la signature. C'est utile dans des environnements restreints où vous ne pouvez pas installer PyJWT, ou pour des scripts de débogage rapide.

Python
import base64, json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.signature"
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)  # fix padding
claims = json.loads(base64.urlsafe_b64decode(payload_b64))
print(claims)  # {'sub': 'usr_8f2a'}

Comment valider le claim audience avec PyJWT ?

Passez le paramètre audience à jwt.decode() : jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). PyJWT compare le claim aud dans le token avec la valeur que vous fournissez. Si le token n'a pas de claim aud, ou si la valeur ne correspond pas, il lève jwt.InvalidAudienceError. Vous pouvez également passer une liste d'audiences acceptables si votre service accepte des tokens destinés à plusieurs APIs.

Python
import jwt

payload = jwt.decode(
    token,
    secret,
    algorithms=["HS256"],
    audience=["https://api.example.com", "https://admin.example.com"]
)

Outils associés

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria SantosRéviseur technique

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.