JWT Decoder Python — Dekodowanie JWT z PyJWT
Użyj darmowego Dekoder JWT bezpośrednio w przeglądarce — bez instalacji.
Wypróbuj Dekoder JWT online →Każde API używające uwierzytelniania opartego na tokenach w pewnym momencie przekazuje ci JWT, a sprawdzenie jego zawartości to jedno z zadań, które pojawiają się nieustannie podczas programowania. Dekoder JWT w Pythonie zamienia ten nieprzejrzysty ciąg base64 w czytelny słownik claims, z którym możesz faktycznie pracować. Pakiet PyPI, którego szukasz, to PyJWT — instalowany poleceniem pip install PyJWT, ale importowany jako import jwt. Przewodnik omawia jwt.decode() z pełną weryfikacją podpisu, dekodowanie bez klucza do szybkiej inspekcji, ręczne dekodowanie base64 bez żadnej biblioteki, weryfikację kluczem publicznym RS256 oraz typowe pułapki, na które natknąłem się w produkcyjnych systemach autoryzacji. Do szybkiego jednorazowego sprawdzenia, online JWT Decoder robi to natychmiast bez pisania kodu. Wszystkie przykłady dotyczą Pythona 3.10+ i PyJWT 2.x.
- ✓pip install PyJWT, następnie import jwt — nazwa pakietu i nazwa importu są różne, co myli prawie każdego.
- ✓jwt.decode(token, key, algorithms=["HS256"]) zwraca zwykły dict z claims. Zawsze przekazuj algorithms jawnie.
- ✓Aby sprawdzić claims bez weryfikacji: jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
- ✓Dla tokenów RSA/EC: pip install PyJWT cryptography — backend cryptography jest wymagany dla algorytmów asymetrycznych.
- ✓Ręczne dekodowanie (base64 + json) działa bez żadnej biblioteki, ale pomija całą walidację podpisu i wygaśnięcia.
Czym jest dekodowanie JWT?
JSON Web Token to trzy segmenty zakodowane w base64url, oddzielone kropkami: nagłówek (algorytm i typ tokenu), payload (claims — identyfikator użytkownika, role, czas wygaśnięcia) oraz podpis. Dekodowanie JWT polega na wyodrębnieniu segmentów nagłówka i payload, zdekodowaniu ich z base64url i sparsowaniu wynikowego JSON do słownika claims.
Nagłówek informuje, który algorytm został użyty do podpisania tokenu i czasem zawiera kid (identyfikator klucza) do znalezienia właściwego klucza weryfikacyjnego. Payload zawiera właściwe dane: komu token został wydany (sub), kiedy wygasa (exp), dla której usługi jest przeznaczony (aud), a także wszelkie własne claims zdefiniowane przez aplikację. Segment podpisu dowodzi, że token nie został zmodyfikowany, ale do jego weryfikacji potrzebujesz klucza tajnego lub klucza publicznego. Dekodowanie i weryfikacja to odrębne operacje. Możesz zdekodować payload bez weryfikacji podpisu (przydatne podczas debugowania), ale nigdy nie ufaj niezweryfikowanym claims przy podejmowaniu decyzji o autoryzacji.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
"sub": "usr_8f2a",
"role": "admin",
"exp": 1711815600
}jwt.decode() — Dekodowanie i weryfikacja z PyJWT
jwt.decode() to główna funkcja biblioteki PyJWT. Przyjmuje zakodowany ciąg tokenu, klucz tajny (dla algorytmów HMAC) lub klucz publiczny (dla RSA/EC) oraz obowiązkową listę algorithms. Funkcja weryfikuje podpis, sprawdza standardowe claims, takie jak exp i nbf, i zwraca payload jako słownik Pythona. Jeśli cokolwiek się nie powiedzie — zły podpis, wygasły token, nieznany algorytm — zgłasza konkretny wyjątek.
Minimalny działający przykład
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"])
# adminParametr algorithms to lista, nie pojedynczy ciąg, i jest obowiązkowy w PyJWT 2.x. To funkcja bezpieczeństwa: bez niej atakujący mógłby spreparować token z alg: none w nagłówku i całkowicie ominąć weryfikację. Zawsze wskazuj dokładnie, które algorytmy akceptuje twoja aplikacja. Jeśli wystawiasz wyłącznie tokeny HS256, lista powinna zawierać ["HS256"] — nie ["HS256", "RS256", "none"]. Ograniczenie listy zmniejsza powierzchnię ataku.
Jedna rzecz, która na początku mnie myliła: PyJWT 2.x zmienił jwt.encode() tak, by zwracał ciąg zamiast bajtów. Jeśli trafisz na stare odpowiedzi na Stack Overflow wywołujące .decode("utf-8") na zakodowanym tokenie, to kod z epoki PyJWT 1.x, który w wersji 2.x zgłosi AttributeError. Token jest już ciągiem — po prostu go użyj.
Pełny cykl z wygaśnięciem tokenu
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")datetime na uniksowe znaczniki czasu podczas kodowania. Podczas dekodowania claims exp, iat i nbf wracają jako liczby całkowite, nie obiekty datetime. Musisz samodzielnie je przekonwertować za pomocą datetime.fromtimestamp(payload["exp"], tz=timezone.utc).Dekodowanie JWT bez weryfikacji podpisu
Czasem musisz odczytać claims, zanim możesz zweryfikować token. Typowy scenariusz: nagłówek tokenu zawiera pole kid (identyfikator klucza) i przed weryfikacją musisz pobrać pasujący klucz publiczny z endpointu JWKS. PyJWT obsługuje to za pomocą opcji verify_signature: False. Nadal przekazujesz listę algorithms, ale argument key jest ignorowany.
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 endpointIstnieje tu subtelna różnica. jwt.get_unverified_header() odczytuje tylko nagłówek — pierwszy segment. Wywołanie jwt.decode() z opcją verify_signature: False odczytuje payload (drugi segment). Łącząc oba wywołania, możesz wyodrębnić wszystko z tokenu bez klucza. PyJWT nadal sprawdza, czy token ma poprawną strukturę (trzy segmenty oddzielone kropkami, prawidłowy base64, prawidłowy JSON), nawet gdy weryfikacja podpisu jest wyłączona. Jeśli token jest strukturalnie zniekształcony, biblioteka zgłasza DecodeError niezależnie od przekazanych opcji.
Dokumentacja parametrów jwt.decode()
Pełna sygnatura to jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require). Wszystkie parametry po algorithms są tylko słowami kluczowymi (keyword-only).
Słownik options daje precyzyjną kontrolę nad tym, które walidacje wykonuje PyJWT. Klucze odpowiadają poszczególnym sprawdzeniom: verify_signature, verify_exp, verify_nbf, verify_iss, verify_aud oraz verify_iat. Wszystkie domyślnie przyjmują wartość True, chyba że jawnie ustawisz je na False. Na produkcji pozostaw wszystkie ustawienia domyślne. Wyłączam poszczególne sprawdzenia tylko podczas programowania, gdy pracuję z nieaktualnymi tokenami testowymi i muszę tymczasowo ominąć sprawdzanie wygaśnięcia.
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
)Ręczne dekodowanie JWT z base64 i json
Możesz zdekodować payload JWT korzystając wyłącznie ze standardowej biblioteki Pythona — bez pip install. Jest to naprawdę przydatne w kilku sytuacjach: skryptach diagnostycznych, gdzie dodanie zależności to przesada, ograniczonych środowiskach CI z dostępem tylko do biblioteki standardowej, funkcjach AWS Lambda, gdzie chcesz zminimalizować czas zimnego startu, albo po prostu gdy chcesz zrozumieć, czym JWT naprawdę jest pod spodem. Proces jest prosty: podziel po kropkach, weź interesujący segment, dodaj padding base64, zdekoduj i sparsuj JSON.
Moduły base64 i json są częścią standardowej biblioteki Pythona, więc to podejście działa na każdej instalacji Pythona od wersji 3.6. Poniższe funkcje obsługują osobno nagłówek (pierwszy segment) i payload (drugi segment):
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: adminSztuczka z paddingiem (+= "=" * (-len(s) % 4)) to element, o którym wszyscy zapominają. JWT base64url pomija końcowe znaki =, ale Python's urlsafe_b64decode ich wymaga. Bez naprawy paddingu otrzymasz błąd binascii.Error: Incorrect padding.
jwt.decode() z prawdziwym kluczem.Dekodowanie JWT z odpowiedzi API i plików tokenów
Dwa najczęstsze scenariusze z życia wzięte: wyodrębnianie JWT z odpowiedzi HTTP (endpoint OAuth, API logowania) oraz odczytywanie tokenów z plików (dane uwierzytelniające kont serwisowych, sekrety montowane przez Kubernetes, tokeny buforowane na dysku). Oba wymagają właściwej obsługi błędów. Żądania sieciowe zawodzą. Pliki znikają. Tokeny wygasają między zapisem w buforze a odczytem.
Poniższe przykłady używają httpx do wywołań HTTP (możesz zastąpić go requests — wzorzec jest identyczny) i pathlib.Path do operacji na plikach. Każdy przykład przechwytuje konkretne wyjątki PyJWT zamiast ogólnego except Exception, dzięki czemu możesz właściwie reagować na każdy rodzaj błędu: ponowna autoryzacja przy wygaśnięciu, alert przy błędzie podpisu, ponowna próba przy przekroczeniu limitu czasu sieci.
Dekodowanie JWT z odpowiedzi API
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')}")Dekodowanie JWT z pliku
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']}")Dekodowanie JWT w wierszu poleceń
Czasem wystarczy rzucić okiem na token z terminala bez pisania skryptu. Może debugujesz przepływ OAuth i chcesz sprawdzić, co jest w nagłówku Authorization, albo pobrałeś token z narzędzi deweloperskich przeglądarki i chcesz sprawdzić jego wygaśnięcie. Flaga -c Pythona pozwala zrobić to w jednej linii. Potokuj token i otrzymaj claims jako sformatowany JSON. Bez pliku skryptu, bez środowiska wirtualnego.
# 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"Jako wizualną alternatywę bez konfiguracji terminala, wklej token do narzędzia ToolDeck JWT Decoder i natychmiast zobacza nagłówek, payload oraz status weryfikacji podpisu.
python-jose i inne alternatywy
python-jose to alternatywna biblioteka JWT obsługująca natywnie JWS, JWE (tokeny szyfrowane) i JWK. Jeśli twoja aplikacja musi obsługiwać szyfrowane JWT (JWE) — gdzie sam payload jest zaszyfrowany, a nie tylko podpisany — python-jose jest właściwym wyborem, ponieważ PyJWT w ogóle nie obsługuje JWE. Biblioteka ma również wbudowaną obsługę zestawów kluczy JWKS, co upraszcza integrację z dostawcami tożsamości, takimi jak Auth0, Okta lub Keycloak, które udostępniają rotujące zestawy kluczy. Interfejs dekodowania jest niemal identyczny z PyJWT, więc przejście między nimi wymaga minimalnych zmian w kodzie:
# 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']}")Moja rekomendacja: zacznij od PyJWT. Pokrywa 95% przypadków użycia JWT, ma największą społeczność i czysty API. Przejdź na python-jose, jeśli potrzebujesz obsługi JWE lub wolisz jego mechanizm JWKS. Trzecia opcja warta wzmianki to Authlib, która łączy obsługę JWT w ramach znacznie większego frameworka OAuth/OIDC. Jeśli już używasz Authlib do przepływów klienta OAuth, jego moduł authlib.jose.jwt pozwoli ci uniknąć dodawania drugiej zależności JWT. W przeciwnym razie jest to zbyt duża zależność jak na samo dekodowanie tokenów.
Kolorowe wyjście w terminalu
Odczytywanie surowych claims JWT w terminalu jest w porządku do szybkich sprawdzeń, ale gdy regularnie debugujesz payloady tokenów (robiłem to codziennie podczas budowania wewnętrznej bramki autoryzacji), kolorowane wyjście robi prawdziwą różnicę. Wartości łańcuchowe, liczby, wartości boolowskie i null renderują się w różnych kolorach, dzięki czemu brakujące uprawnienie lub błędny znacznik czasu wygaśnięcia widać od razu bez czytania każdego znaku.
Biblioteka rich (pip install rich) ma funkcję print_json, która przyjmuje ciąg JSON lub słownik Pythona i wyświetla go w terminalu z pełnym podświetlaniem składni. Połącz ją z PyJWT, by uzyskać dwuliniowy przepływ pracy do inspekcji JWT:
# 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
# }rich zawiera kody ANSI. Nie zapisuj go do plików ani nie zwracaj z endpointów API — służy wyłącznie do wyświetlania w terminalu. Gdy potrzebujesz zwykłego tekstu, użyj json.dumps().Praca z dużymi zbiorami tokenów
Same tokeny JWT są niewielkie (zazwyczaj poniżej 2 KB), ale zdarzają się scenariusze, w których przetwarzasz je masowo. Analiza dziennika audytu po incydencie bezpieczeństwa. Skrypty migracji sesji przy zmianie dostawcy autoryzacji. Zbiorcza walidacja zgodności, gdzie musisz udowodnić, że każdy token wystawiony w ciągu ostatnich 90 dni był podpisany właściwym kluczem. Jeśli masz dziesiątki tysięcy tokenów w pliku dziennika NDJSON, przetwarzanie linia po linii pozwala uniknąć ładowania całego pliku do pamięci i umożliwia raportowanie wyników przyrostowo.
Zbiorcza walidacja tokenów z dziennika audytu
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: 17Wyodrębnianie claims z eksportu tokenów NDJSON
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 do rozdzielenia walidacji na wiele rdzeni — każdy token jest niezależny.Częste błędy
Te cztery błędy pojawiają się regularnie podczas przeglądów kodu i w pytaniach na Stack Overflow. Każdy z nich jest łatwy do popełnienia, a komunikat błędu PyJWT nie zawsze wskazuje bezpośrednio na przyczynę. Sam widziałem, jak problem z nazwą pakietu marnował godziny debugowania, gdy ktoś instalował złą bibliotekę i otrzymywał zupełnie nieoczekiwany interfejs API.
Problem: Uruchomienie pip install jwt lub pip install python-jwt instaluje zupełnie inny pakiet. import jwt następnie zawodzi lub daje API, którego nie rozpoznajesz.
Rozwiązanie: Zawsze instaluj z pip install PyJWT. Import to import jwt. Sprawdź za pomocą pip show PyJWT, czy zainstalowany jest właściwy pakiet.
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
Problem: W PyJWT 1.x parametr algorithms był opcjonalny i domyślnie zezwalał na dowolny algorytm. Stworzyło to lukę bezpieczeństwa, w której atakujący mógł ustawić alg: none. PyJWT 2.x zgłasza teraz DecodeError, gdy algorithms jest pominięty.
Rozwiązanie: Zawsze przekazuj algorithms jako jawną listę. Używaj tylko algorytmów, dla których twoja aplikacja rzeczywiście wystawia tokeny.
# 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"])
Problem: Przekazanie ciągu jako klucza tajnego do jwt.decode() z algorithms=["RS256"] zgłasza InvalidSignatureError. RS256 wymaga klucza publicznego w formacie PEM, a nie ciągu współdzielonego sekretu.
Rozwiązanie: Wczytaj klucz publiczny PEM z pliku lub zmiennej środowiskowej. Zainstaluj pakiet cryptography: pip install PyJWT cryptography.
# 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"])Problem: Kodowanie base64url stosowane w JWT pomija końcowe znaki =. Funkcja Python's base64.urlsafe_b64decode zgłasza binascii.Error: Incorrect padding, jeśli przekażesz surowy segment bez naprawy paddingu.
Rozwiązanie: Dodaj padding przed dekodowaniem: segment += '=' * (-len(segment) % 4). Ta formuła zawsze generuje prawidłową liczbę znaków paddingu (0, 1, 2 lub 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 alternatywy — szybkie porównanie
PyJWT to właściwy punkt startowy dla większości aplikacji Pythona. Pokrywa HMAC oraz (z backendem cryptography) weryfikację podpisów RSA i EC. Jeśli potrzebujesz JWE (tokeny szyfrowane), przejdź na python-jose lub Authlib. Ręczne dekodowanie base64 sprawdza się do debugowania, ale nie daje żadnych gwarancji bezpieczeństwa.
Kiedy sięgam po poszczególne opcje: PyJWT dla każdej standardowej usługi webowej wykonującej weryfikację HS256 lub RS256. python-jose, gdy architektura obejmuje tokeny szyfrowane lub rotację JWKS. Ręczne dekodowanie base64 do szybkiej inspekcji w środowiskach bez dostępu do pip (kontenery CI, ograniczone hosty produkcyjne, zimne starty AWS Lambda, gdzie chcesz zminimalizować zależności). Authlib, gdy projekt już go używa do przepływów klienta OAuth i dodawanie kolejnej biblioteki JWT byłoby zbędne.
Jako alternatywę bez kodu, wklej dowolny token do narzędzia JWT Decoder, by zobaczyć zdekodowany nagłówek i payload wraz z informacją zwrotną o walidacji claims.
Najczęściej zadawane pytania
Jak zdekodować JWT w Pythonie bez weryfikacji podpisu?
Przekaż options={"verify_signature": False} oraz algorithms=["HS256"] do jwt.decode(). Funkcja zwróci słownik payload bez weryfikowania podpisu. Używaj tego wyłącznie do inspekcji — odczytu claims przed pobraniem właściwego klucza publicznego lub podczas debugowania w środowisku deweloperskim. Nigdy nie pomijaj weryfikacji dla tokenów kontrolujących dostęp do czegokolwiek.
import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
payload = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}Jaka jest różnica między PyJWT a python-jwt?
PyJWT (pip install PyJWT, import jwt) to najpopularniejsza biblioteka JWT dla Pythona z ponad 80 milionami pobrań miesięcznie. python-jwt (pip install python_jwt) to osobna, znacznie rzadziej używana biblioteka z innym interfejsem API. Jeśli widzisz import jwt w czymś kodzie, autor używa PyJWT. Zamieszanie wynika z tego, że nazwa pakietu w PyPI (PyJWT) różni się od nazwy importu (jwt). Korzystaj z PyJWT, chyba że masz konkretny powód, by sięgnąć po inną bibliotekę.
Jak zdekodować JWT z RS256 w Pythonie?
Zainstaluj PyJWT oraz backend cryptography: pip install PyJWT cryptography. Następnie przekaż klucz publiczny w formacie PEM jako argument key oraz algorithms=["RS256"]. PyJWT deleguje weryfikację podpisu RSA do biblioteki cryptography. Bez zainstalowanego pakietu cryptography PyJWT zgłosi błąd przy próbie użycia algorytmów RSA lub EC.
import jwt
public_key = open("public_key.pem").read()
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience="https://api.example.com"
)Dlaczego PyJWT zgłasza ExpiredSignatureError?
PyJWT domyślnie sprawdza claim exp (wygaśnięcie). Jeśli bieżący czas UTC przekroczył znacznik czasu exp, biblioteka zgłasza jwt.ExpiredSignatureError. Możesz dodać tolerancję rozbieżności zegarów za pomocą parametru leeway: jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Daje to 30-sekundowy margines. Aby całkowicie wyłączyć sprawdzanie wygaśnięcia (niezalecane na produkcji), przekaż options={"verify_exp": False}.
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")Czy mogę odczytać claims JWT bez żadnej biblioteki w Pythonie?
Tak. Podziel token po kropkach, weź drugi segment (payload), uzupełnij go znakami = tak, by długość była wielokrotnością 4, następnie zdekoduj base64url i sparsuj JSON. Otrzymasz słownik claims, ale podpis nie zostanie zweryfikowany. Jest to przydatne w środowiskach z ograniczeniami, gdzie nie można zainstalować PyJWT, lub do szybkich skryptów diagnostycznych.
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'}Jak walidować claim audience za pomocą PyJWT?
Przekaż parametr audience do jwt.decode(): jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). PyJWT porównuje claim aud w tokenie z podaną wartością. Jeśli token nie zawiera claim aud lub wartości się nie zgadzają, biblioteka zgłasza jwt.InvalidAudienceError. Możesz też przekazać listę akceptowanych audiences, jeśli usługa obsługuje tokeny przeznaczone dla wielu API.
import jwt
payload = jwt.decode(
token,
secret,
algorithms=["HS256"],
audience=["https://api.example.com", "https://admin.example.com"]
)Powiązane narzędzia
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.