JWT Decoder Python — JWTs decoderen met PyJWT

·DevOps Engineer & Python Automation Specialist·Beoordeeld doorMaria Santos·Gepubliceerd

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.

Before · json
After · json
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

Python 3.10+
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"])
# admin

De 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

Python 3.10+ — encode then decode with exp
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")
Opmerking:PyJWT converteert 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.

Python 3.10+ — unverified decode
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-endpoint
Waarschuwing:Niet-geverifieerde tokens zijn niet betrouwbaar. Gebruik dit patroon alleen voor routeringsbeslissingen (welke sleutel op te halen, welke tenant op te zoeken). Maak nooit autorisatiebeslissingen op basis van niet-geverifieerde claims. Een aanvaller kan alles in de payload zetten wat hij wil.

Er 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.

Parameter
Type
Standaard
Beschrijving
jwt
str | bytes
(verplicht)
De gecodeerde JWT-string om te decoderen
key
str | bytes | dict
(verplicht)
Geheime sleutel (HMAC) of publieke sleutel (RSA/EC) voor verificatie
algorithms
list[str]
(verplicht)
Toegestane algoritmen — bijv. ["HS256"], ["RS256"]. Laat dit nooit weg.
options
dict
{}
Overschrijf verificatievlaggen: verify_signature, verify_exp, verify_aud, etc.
audience
str | list[str]
None
Verwachte aud-claim — geeft InvalidAudienceError als het niet overeenkomt
issuer
str
None
Verwachte iss-claim — geeft InvalidIssuerError als het niet overeenkomt
leeway
timedelta | int
0
Seconden tolerantie voor klokafwijking bij exp- en nbf-controles
require
list[str]
[]
Claims die aanwezig moeten zijn — geeft MissingRequiredClaimError als ze ontbreken

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.

Python 3.10+ — using options and require
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:

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

De 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.

Opmerking:Handmatig decoderen verifieert de handtekening niet. Iedereen kan de payload van een JWT aanpassen en opnieuw coderen. Gebruik deze aanpak alleen voor inspectie en debuggen, nooit voor autorisatielogica. Voor alles wat er toe doet, gebruik 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

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')}")

Een JWT decoderen uit een bestand

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']}")

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.

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"

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:

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']}")

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:

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"]
)

# 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
# }
Opmerking: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

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

Claims extraheren uit een NDJSON-tokenexport

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")
Opmerking:Voor bestanden tot een paar honderd MB is regel-voor-regel lezen efficiënt genoeg. Als je prestatiegrenzen bereikt bij zeer grote tokendumps, overweeg dan 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.

PyJWT en python-jwt pakketnamen door elkaar halen

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.

Before · Python
After · Python
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
De algorithms-parameter weglaten

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.

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"])
jwt.decode() gebruiken met het verkeerde sleuteltype voor RS256

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.

Before · Python
After · Python
# 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"])
base64-opvulling vergeten bij handmatig decoderen

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).

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. alternatieven — snel vergelijk

Methode
Verifieert handtekening
Valideert claims
Aangepaste typen
Installatie vereist
PyJWT jwt.decode()
✓ (exp, aud, iss, nbf)
N/A (geeft dict terug)
pip install PyJWT
PyJWT geverifieerd decoderen
N/A
pip install PyJWT
Handmatig base64 decoderen
N/A
Nee (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 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.

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'}

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.

Python
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.

Python
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.

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'}

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.

Python
import jwt

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

Gerelateerde tools

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 SantosTechnisch beoordelaar

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.