JWT Decoder Python — JWTs decoderen met PyJWT
Gebruik de gratis JWT Decoder direct in je browser — geen installatie nodig.
JWT Decoder online uitproberen →Elke API die gebruik maakt van tokengebaseerde authenticatie geeft je op een gegeven moment een JWT, en uitzoeken wat erin zit is een taak die tijdens ontwikkeling voortdurend terugkomt. Een JWT-decoder in Python neemt die ondoorzichtige base64-string en maakt er een leesbaar claims-woordenboek van waarmee je echt kunt werken. Het PyPI-pakket dat je nodig hebt is PyJWT — te installeren met pip install PyJWT maar te importeren als import jwt. Deze handleiding behandelt jwt.decode() met volledige handtekeningverificatie, decoderen zonder geheim voor snelle inspectie, handmatig base64-decoderen zonder bibliotheek, RS256-verificatie met publieke sleutel, en veelgemaakte fouten die ik in productie-auth-systemen ben tegengekomen. Voor een snelle eenmalige controle doet de online JWT Decoder dit direct zonder code. Alle voorbeelden zijn gericht op Python 3.10+ en PyJWT 2.x.
- ✓pip install PyJWT, daarna import jwt — de pakketnaam en importnaam zijn verschillend, wat bijna iedereen in de war brengt.
- ✓jwt.decode(token, key, algorithms=["HS256"]) geeft een gewone dict met de claims terug. Geef algorithms altijd expliciet mee.
- ✓Om claims te inspecteren zonder verificatie: jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
- ✓Voor RSA/EC-tokens: pip install PyJWT cryptography — de cryptography-backend is vereist voor asymmetrische algoritmen.
- ✓Handmatig decoderen (base64 + json) werkt zonder bibliotheek, maar slaat alle handtekening- en vervalvalidatie over.
Wat is JWT-decodering?
Een JSON Web Token bestaat uit drie base64url-gecodeerde segmenten gescheiden door punten: een header (algoritme en tokentype), een payload (de claims — gebruikers-ID, rollen, vervaltijd), en een handtekening. Een JWT decoderen betekent de header- en payload-segmenten extraheren, ze base64url-decoderen en de resulterende JSON omzetten in een woordenboek van claims.
De header vertelt je welk algoritme is gebruikt om de token te ondertekenen en bevat soms een kid (sleutel-ID) om de juiste verificatiesleutel te vinden. De payload bevat de werkelijke gegevens: aan wie de token is uitgegeven (sub), wanneer hij vervalt (exp), voor welke dienst hij bedoeld is (aud), plus eventuele aangepaste claims die je applicatie definieert. Het handtekeningsegment bewijst dat de token niet is gewijzigd, maar je hebt de geheime sleutel of publieke sleutel nodig om dit te verifiëren. Decoderen en verificatie zijn aparte bewerkingen. Je kunt de payload decoderen zonder de handtekening te verifiëren (handig voor debuggen), maar vertrouw nooit niet-geverifieerde claims voor autorisatiebeslissingen.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
"sub": "usr_8f2a",
"role": "admin",
"exp": 1711815600
}jwt.decode() — Decoderen en verifiëren met PyJWT
jwt.decode() is de primaire functie van de PyJWT-bibliotheek. Ze neemt de gecodeerde tokenstring, de geheime sleutel (voor HMAC-algoritmen) of publieke sleutel (voor RSA/EC), en een verplichte algorithms-lijst. De functie verifieert de handtekening, controleert standaardclaims zoals exp en nbf, en geeft de payload terug als een Python-woordenboek. Als er iets misgaat — slechte handtekening, verlopen token, verkeerd algoritme — gooit ze een specifieke uitzondering.
Minimaal werkend voorbeeld
import jwt
# Een gedeeld geheim tussen de uitgever en deze dienst
SECRET_KEY = "k8s-webhook-signing-secret-2026"
# Codeer eerst een token (simuleert wat een auth-server zou uitgeven)
token = jwt.encode(
{"sub": "usr_8f2a", "role": "admin", "team": "platform"},
SECRET_KEY,
algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3Jf...
# Decodeer en verifieer de token
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin', 'team': 'platform'}
print(payload["role"])
# adminDe algorithms parameter is een lijst, geen enkele string, en is verplicht in PyJWT 2.x. Dit is een beveiligingsfunctie: zonder dit kan een aanvaller een token maken met alg: none in de header en verificatie volledig omzeilen. Geef altijd precies aan welke algoritmen je applicatie accepteert. Als je alleen HS256-tokens uitgeeft, moet de lijst ["HS256"] zijn — niet ["HS256", "RS256", "none"]. Een beperkte lijst verkleint het aanvalsoppervlak.
Iets dat me vroeger verward heeft: PyJWT 2.x heeft jwt.encode() gewijzigd zodat het een string teruggeeft in plaats van bytes. Als je oude Stack Overflow-antwoorden leest waarbij .decode("utf-8") wordt aangeroepen op de gecodeerde token, stamt die code uit het PyJWT 1.x-tijdperk en geeft een AttributeError in versie 2.x. De token is al een string — gebruik hem gewoon direct.
Volledige roundtrip met vervaldatum
import jwt
from datetime import datetime, timedelta, timezone
SECRET_KEY = "webhook-processor-secret"
# Maak een token aan dat over 1 uur vervalt
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, wanneer de token binnenkomt in een 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 verlopen — vraag opnieuw om authenticatie")
except jwt.InvalidAudienceError:
print("Token niet bedoeld voor deze API")
except jwt.InvalidIssuerError:
print("Token uitgegeven door onbekende instantie")datetime-objecten automatisch naar Unix-tijdstempels tijdens het coderen. Tijdens het decoderen komen de exp-, iat- en nbf-claims terug als gehele getallen, niet als datetime-objecten. Je moet ze zelf omzetten met datetime.fromtimestamp(payload["exp"], tz=timezone.utc).Een JWT decoderen zonder handtekeningverificatie
Soms moet je de claims lezen voordat je de token kunt verifiëren. Een veelvoorkomend scenario: de tokenheader bevat een kid (sleutel-ID) veld en je moet de bijbehorende publieke sleutel ophalen van een JWKS-endpoint voordat je kunt verifiëren. PyJWT ondersteunt dit met de verify_signature: False optie. Je geeft nog steeds de algorithms-lijst mee, maar het key-argument wordt genegeerd.
import jwt
token = (
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZy0xNzI2In0"
".eyJzdWIiOiJ1c3JfM2M3ZiIsInNjb3BlIjoicmVhZDpvcmRlcnMiLCJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIn0"
".signature_placeholder"
)
# Stap 1: Claims lezen zonder verificatie om routeringsinformatie te verkrijgen
unverified = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["RS256"]
)
print(unverified)
# {'sub': 'usr_3c7f', 'scope': 'read:orders', 'iss': 'auth.example.com'}
# Stap 2: Lees de header om te zien welke sleutel je moet gebruiken
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'RS256', 'typ': 'JWT', 'kid': 'sig-1726'}
# Gebruik nu header['kid'] om de juiste publieke sleutel op te halen van je JWKS-endpointEr is hier een subtiel verschil. jwt.get_unverified_header() leest alleen de header — het eerste segment. De jwt.decode()-aanroep met verify_signature: False leest de payload (het tweede segment). Samen kun je alles uit een token extraheren zonder een sleutel. PyJWT valideert nog steeds dat de token de juiste structuur heeft (drie punt-gescheiden segmenten, geldige base64, geldige JSON) ook als handtekeningverificatie uitgeschakeld is. Als de token structureel onjuist is, gooit het DecodeError ongeacht de opties die je doorgeeft.
jwt.decode() parameterreferentie
De volledige signatuur is jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require). Alle parameters na algorithms zijn keyword-only.
De options-dict geeft nauwkeurige controle over welke validaties PyJWT uitvoert. De sleutels corresponderen met afzonderlijke controles: verify_signature, verify_exp, verify_nbf, verify_iss, verify_aud, en verify_iat. Alle staan standaard op True tenzij je ze expliciet op False zet. Laat ze in productie allemaal op de standaardwaarden staan. De enige keer dat ik afzonderlijke controles uitschakel is tijdens ontwikkeling wanneer ik werk met verouderde testtokens en de vervaldatumcontrole tijdelijk moet omzeilen.
import jwt
# Vereis dat specifieke claims aanwezig zijn — gooit MissingRequiredClaimError als ze ontbreken
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"],
options={"require": ["exp", "iss", "sub"]},
issuer="auth.internal.example.com",
)
# Alleen tijdens ontwikkeling: sla vervaldatum over om te testen met oude tokens
dev_payload = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"],
options={"verify_exp": False}, # NIET gebruiken in productie
)Handmatig JWT-decoderen met base64 en json
Je kunt een JWT-payload decoderen met niets anders dan de Python-standaardbibliotheek — geen pip install nodig. Dit is echt nuttig in verschillende situaties: debugscripts waarbij een afhankelijkheid te zwaar is, beperkte CI-omgevingen waar alleen de standaardbibliotheek beschikbaar is, AWS Lambda-functies waarbij je de cold-start-tijd wilt minimaliseren, of simpelweg begrijpen wat een JWT eigenlijk is onder de motorkap. Het proces is eenvoudig: splits op punten, neem het gewenste segment, voeg base64-opvulling toe, decodeer en parseer de JSON.
De base64- en json-modules zitten beide in de Python-standaardbibliotheek, dus deze aanpak werkt op elke Python-installatie vanaf 3.6. De onderstaande functies behandelen de header (eerste segment) en payload (tweede segment) afzonderlijk:
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: adminDe opvullingsformule (+= "=" * (-len(s) % 4)) is het stuk dat iedereen vergeet. JWT base64url laat afsluitende =-tekens weg, maar Python's urlsafe_b64decode vereist ze. Zonder de opvullingscorrectie krijg je een binascii.Error: Incorrect padding.
jwt.decode() met een echte sleutel.JWTs decoderen uit API-responses en tokenbestanden
De twee meest voorkomende scenario's in de praktijk: een JWT extraheren uit een HTTP-response (een OAuth-tokenendpoint, een login-API) en tokens lezen uit bestanden (serviceaccount- credentials, Kubernetes-gemonteerde secrets, gecachede tokens op schijf). Beide vereisen foutafhandeling. Netwerkverzoeken mislukken. Bestanden verdwijnen. Tokens verlopen tussen het moment van cachen en het moment van lezen.
De onderstaande voorbeelden gebruiken httpx voor HTTP-aanroepen (vervang door requests als je dat prefereert, het patroon is identiek) en pathlib.Path voor bestandsbewerkingen. Elk voorbeeld vangt specifieke PyJWT-uitzonderingen op in plaats van een generieke except Exception, zodat je passend kunt reageren op elke fout: opnieuw authenticeren bij verlopen token, waarschuwen bij handtekeningfout, opnieuw proberen bij netwerktimeout.
Een JWT decoderen uit een API-response
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')}")Een JWT decoderen uit een bestand
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']}")JWT decoderen via de commandoregel
Soms wil je gewoon even in een token kijken vanuit de terminal zonder een script te schrijven. Misschien debug je een OAuth-flow en wil je zien wat er in de Authorization-header zit, of je hebt een token uit browser-DevTools gehaald en wilt de vervaldatum controleren. Python's -c-vlag maakt dit tot een one-liner. Pipe de token naar binnen en krijg de claims als opgemaakte JSON. Geen scriptbestand nodig, geen virtuele omgeving.
# 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"
# }# 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"
# }# 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"Als visueel alternatief zonder terminalinstellingen, plak je token in de ToolDeck JWT Decoder en zie direct de header, payload en handtekeningverificatiestatus.
python-jose en andere alternatieven
python-jose is een alternatieve JWT-bibliotheek die JWS, JWE (versleutelde tokens) en JWK native ondersteunt. Als je applicatie versleutelde JWTs (JWE) moet verwerken — waarbij de payload zelf versleuteld is, niet alleen ondertekend — is python-jose de juiste keuze omdat PyJWT JWE helemaal niet ondersteunt. De bibliotheek heeft ook ingebouwde JWKS-sleutelsetverwerking, wat integratie vereenvoudigt met identity providers zoals Auth0, Okta of Keycloak die roterende sleutelsets blootstellen. De decode-interface is vrijwel identiek aan PyJWT, dus overschakelen vereist minimale codewijzigingen:
# 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']}")Mijn aanbeveling: begin met PyJWT. Het dekt 95% van de JWT-gebruiksscenario's, heeft de grootste community en de API is overzichtelijk. Schakel over naar python-jose als je JWE-ondersteuning nodig hebt of de voorkeur geeft aan de JWKS-verwerking ervan. Een derde optie die het vermelden waard is, is Authlib, dat JWT-verwerking bundelt in een veel groter OAuth/OIDC-framework. Als je Authlib al gebruikt voor OAuth-clientflows, bespaart de authlib.jose.jwt-module je een tweede JWT-afhankelijkheid. Anders is het een zware afhankelijkheid puur voor tokendecodering.
Terminaluitvoer met syntaxiskleuring
Ruwe JWT-claims lezen in een terminal is prima voor snelle controles, maar als je regelmatig tokenpayloads debugt (ik deed dit dagelijks bij het bouwen van een intern auth-gateway), maakt gekleurde uitvoer een wezenlijk verschil. Tekenreekswaarden, getallen, booleans en null worden in afzonderlijke kleuren weergegeven, waardoor je een ontbrekende machtiging of verkeerd vervaltijdstempel in één oogopslag kunt herkennen zonder elk teken te lezen.
De rich-bibliotheek (pip install rich) heeft een print_json-functie die een JSON-string of een Python-dict neemt en dit met volledige syntaxiskleuring in de terminal afdrukt. Combineer het met PyJWT voor een twee-staps JWT-inspectieworkflow:
# 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"]
)
# Gekleurde, ingesprongen JSON-uitvoer in de terminal
print_json(data=payload)
# {
# "sub": "usr_8f2a", ← strings in groen
# "role": "admin",
# "permissions": [
# "orders:read",
# "refunds:create"
# ],
# "exp": 1711815600 ← getallen in cyaan
# }rich-uitvoer bevat ANSI-escapecodes. Schrijf dit niet naar bestanden of stuur het niet terug als API-response — het is alleen voor terminalweergave. Gebruik json.dumps() wanneer je platte tekstuitvoer nodig hebt.Werken met grote tokenbatches
JWT-tokens zelf zijn klein (doorgaans minder dan 2 KB elk), maar er zijn scenario's waarbij je ze in bulk verwerkt. Auditloganalyse na een beveiligingsincident. Sessiemigratiescripts bij het wisselen van auth-provider. Compliance-batchvalidatie waarbij je moet aantonen dat elke token uitgegeven in de afgelopen 90 dagen ondertekend was met de juiste sleutel. Als je tienduizenden tokens in een NDJSON-logbestand hebt, vermijdt regel-voor-regel lezen dat het hele bestand in het geheugen wordt geladen en kun je resultaten incrementeel rapporteren.
Tokens batchgewijs valideren uit een auditlog
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: 17Claims extraheren uit een NDJSON-tokenexport
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")multiprocessing.Pool te gebruiken om validatie over meerdere kernen te verdelen, omdat elke token onafhankelijk is.Veelgemaakte fouten
Deze vier fouten duiken herhaaldelijk op in code-reviews en Stack Overflow-vragen. Elk is gemakkelijk te maken, en de foutmelding die PyJWT geeft wijst niet altijd direct naar de oorzaak. De naamverwarring bij pakketten alleen al heeft uren debugging gekost wanneer iemand de verkeerde bibliotheek installeert en een volledig onverwachte API krijgt.
Probleem: pip install jwt of pip install python-jwt uitvoeren installeert een compleet ander pakket. import jwt mislukt daarna of geeft je een API die je niet herkent.
Oplossing: Installeer altijd met pip install PyJWT. De import is import jwt. Controleer met pip show PyJWT of het juiste pakket geïnstalleerd is.
pip install jwt # or pip install python-jwt import jwt # verkeerd pakket — compleet andere API
pip install PyJWT import jwt # correct — dit is PyJWT print(jwt.__version__) # 2.x
Probleem: In PyJWT 1.x was algorithms optioneel en stond het elk algoritme toe. Dit creëerde een beveiligingskwetsbaarheid waarbij een aanvaller alg: none kon instellen. PyJWT 2.x gooit nu DecodeError als algorithms ontbreekt.
Oplossing: Geef algorithms altijd mee als expliciete lijst. Gebruik alleen de algoritmen die je applicatie daadwerkelijk uitgeeft.
# 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"])
Probleem: Een tekenreeksgeheim meegeven aan jwt.decode() met algorithms=["RS256"] gooit InvalidSignatureError. RS256 vereist een PEM-gecodeerde publieke sleutel, geen gedeeld geheim.
Oplossing: Laad de PEM-publieke sleutel uit een bestand of omgevingsvariabele. Installeer het cryptography-pakket: pip install PyJWT cryptography.
# Dit mislukt — RS256 heeft een publieke sleutel nodig, geen tekenreeksgeheim payload = jwt.decode(token, "my-secret", algorithms=["RS256"]) # InvalidSignatureError
public_key = open("public_key.pem").read()
payload = jwt.decode(token, public_key, algorithms=["RS256"])Probleem: JWT base64url-codering verwijdert afsluitende =-tekens. Python's base64.urlsafe_b64decode gooit binascii.Error: Incorrect padding als je het ruwe segment doorgeeft zonder de opvulling te corrigeren.
Oplossing: Voeg opvulling toe vóór het decoderen: segment += '=' * (-len(segment) % 4). Deze formule geeft altijd het juiste aantal opvullingstekens (0, 1, 2 of 3).
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect paddingimport base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64) # worksPyJWT vs. alternatieven — snel vergelijk
PyJWT is het juiste startpunt voor de meeste Python-applicaties. Het dekt HMAC en (met de cryptography-backend) RSA- en EC-handtekeningverificatie. Als je JWE (versleutelde tokens) nodig hebt, schakel dan over naar python-jose of Authlib. Handmatig base64-decoderen werkt voor debuggen maar biedt geen veiligheidsgaranties.
Dit is wanneer ik voor elke optie kies: PyJWT voor elke standaard webdienst die HS256- of RS256-verificatie doet. python-jose als de architectuur versleutelde tokens of JWKS-rotatie bevat. Handmatig base64 voor snelle inspectie in omgevingen waar pip niet beschikbaar is (CI-containers, beperkte productieservers, AWS Lambda cold starts waarbij je afhankelijkheden wilt minimaliseren). Authlib wanneer het project het al gebruikt voor OAuth-clientflows en een tweede JWT-bibliotheek overbodig zou zijn.
Als code-vrij alternatief, plak elke token in de JWT Decoder om de gedecodeerde header en payload te zien met feedback over claimvalidatie.
Veelgestelde vragen
Hoe decodeer ik een JWT in Python zonder de handtekening te verifiëren?
Geef options={"verify_signature": False} en algorithms=["HS256"] mee aan jwt.decode(). Dit geeft de payload als dict terug zonder de handtekening te controleren. Gebruik dit alleen voor inspectie — claims lezen voordat je de juiste publieke sleutel ophaalt, of voor debuggen tijdens ontwikkeling. Sla verificatie nooit over bij tokens die toegang verlenen tot iets.
import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
payload = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}Wat is het verschil tussen PyJWT en python-jwt?
PyJWT (pip install PyJWT, import jwt) is de meest gebruikte JWT-bibliotheek voor Python met meer dan 80 miljoen downloads per maand. python-jwt (pip install python_jwt) is een aparte, veel minder gebruikte bibliotheek met een ander API-oppervlak. Als je import jwt ziet in iemands code, gebruiken ze PyJWT. De naamverwarring komt doordat de PyPI-pakketnaam (PyJWT) verschilt van de importnaam (jwt). Houd je aan PyJWT tenzij je een specifieke reden hebt om dat niet te doen.
Hoe decodeer ik een JWT met RS256 in Python?
Installeer zowel PyJWT als de cryptography-backend: pip install PyJWT cryptography. Geef daarna de PEM-gecodeerde publieke sleutel mee als key-argument en algorithms=["RS256"]. PyJWT delegeert de RSA-handtekeningverificatie aan de cryptography-bibliotheek. Zonder het cryptography-pakket geeft PyJWT een foutmelding wanneer je RSA- of EC-algoritmen probeert te gebruiken.
import jwt
public_key = open("public_key.pem").read()
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience="https://api.example.com"
)Waarom geeft PyJWT een ExpiredSignatureError?
PyJWT controleert standaard de exp-claim (vervaldatum). Als de huidige UTC-tijd voorbij het exp-tijdstempel is, gooit het jwt.ExpiredSignatureError. Je kunt tolerantie voor klokafwijking toevoegen met de leeway-parameter: jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Dit geeft een grace period van 30 seconden. Om de vervalcontrole volledig uit te schakelen (niet aanbevolen voor productie), geef je options={"verify_exp": False} mee.
import jwt
from datetime import timedelta
try:
payload = jwt.decode(token, secret, algorithms=["HS256"], leeway=timedelta(seconds=30))
except jwt.ExpiredSignatureError:
print("Token is verlopen — vraag opnieuw om authenticatie")Kan ik JWT-claims lezen zonder bibliotheek in Python?
Ja. Splits de token op punten, neem het tweede segment (de payload), vul het aan met =-tekens zodat de lengte een veelvoud van 4 is, decodeer het vervolgens met base64url en parseer de JSON. Dit geeft je de claims als dict, maar verifieert de handtekening niet. Handig in beperkte omgevingen waar je PyJWT niet kunt installeren, of voor snelle debugscripts.
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'}Hoe valideer ik de audience-claim met PyJWT?
Geef de audience-parameter mee aan jwt.decode(): jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). PyJWT vergelijkt de aud-claim in de token met de waarde die je opgeeft. Als de token geen aud-claim heeft, of de waarde niet overeenkomt, gooit het jwt.InvalidAudienceError. Je kunt ook een lijst van toegestane audiences meegeven als jouw dienst tokens voor meerdere API's accepteert.
import jwt
payload = jwt.decode(
token,
secret,
algorithms=["HS256"],
audience=["https://api.example.com", "https://admin.example.com"]
)Gerelateerde tools
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.
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.