JWT Decoder Python — Dekodowanie JWT z PyJWT

·DevOps Engineer & Python Automation Specialist·Sprawdzono przezMaria Santos·Opublikowano

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.

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

Python 3.10+
import jwt

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

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

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

Parametr 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

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

SECRET_KEY = "webhook-processor-secret"

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

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

# Later, when the token arrives in a request header:
try:
    decoded = jwt.decode(
        token,
        SECRET_KEY,
        algorithms=["HS256"],
        audience="https://api.example.com",
        issuer="auth.internal.example.com",
    )
    print(f"Service: {decoded['sub']}")
    print(f"Permissions: {decoded['permissions']}")
except jwt.ExpiredSignatureError:
    print("Token expired — request re-authentication")
except jwt.InvalidAudienceError:
    print("Token not intended for this API")
except jwt.InvalidIssuerError:
    print("Token issued by unknown authority")
Uwaga:PyJWT automatycznie konwertuje obiekty 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.

Python 3.10+ — unverified decode
import jwt

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

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

# Step 2: Read the header to find which key to use
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'RS256', 'typ': 'JWT', 'kid': 'sig-1726'}
# Now use header['kid'] to fetch the correct public key from your JWKS endpoint
Ostrzeżenie:Niezweryfikowane tokeny nie są godne zaufania. Używaj tego wzorca wyłącznie do decyzji routingowych (który klucz pobrać, który tenant wyszukać). Nigdy nie podejmuj decyzji autoryzacyjnych na podstawie niezweryfikowanych claims. Atakujący może umieścić w payload cokolwiek.

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

Parametr
Typ
Domyślnie
Opis
jwt
str | bytes
(wymagane)
Zakodowany ciąg JWT do zdekodowania
key
str | bytes | dict
(wymagane)
Klucz tajny (HMAC) lub klucz publiczny (RSA/EC) do weryfikacji
algorithms
list[str]
(wymagane)
Dozwolone algorytmy — np. ["HS256"], ["RS256"]. Nigdy nie pomijaj tego parametru.
options
dict
{}
Nadpisuje flagi weryfikacji: verify_signature, verify_exp, verify_aud itp.
audience
str | list[str]
None
Oczekiwana wartość claim aud — zgłasza InvalidAudienceError przy niezgodności
issuer
str
None
Oczekiwana wartość claim iss — zgłasza InvalidIssuerError przy niezgodności
leeway
timedelta | int
0
Tolerancja rozbieżności zegarów (w sekundach) dla sprawdzania exp i nbf
require
list[str]
[]
Claims, które muszą być obecne — zgłasza MissingRequiredClaimError, jeśli ich brakuje

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.

Python 3.10+ — using options and require
import jwt

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

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

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

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

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

Uwaga:Ręczne dekodowanie nie weryfikuje podpisu. Każdy może zmienić payload JWT i ponownie go zakodować. Używaj tego podejścia wyłącznie do inspekcji i debugowania, nigdy do logiki autoryzacji. Dla wszystkiego, co ma znaczenie, użyj 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

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

Dekodowanie JWT z pliku

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

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.

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"

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:

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

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:

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

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbIm9yZGVyczpyZWFkIiwicmVmdW5kczpjcmVhdGUiXSwiZXhwIjoxNzExODE1NjAwfQ.sig"

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

# Colorized, indented JSON output in the terminal
print_json(data=payload)
# {
#   "sub": "usr_8f2a",           ← strings in green
#   "role": "admin",
#   "permissions": [
#     "orders:read",
#     "refunds:create"
#   ],
#   "exp": 1711815600            ← numbers in cyan
# }
Uwaga:Wyjście 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

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

Wyodrębnianie claims z eksportu tokenów NDJSON

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")
Uwaga:Dla plików o rozmiarze do kilkuset MB odczyt linia po linii jest wystarczająco wydajny. Jeśli napotkasz limity wydajności przy bardzo dużych zbiorach tokenów, rozważ użycie 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.

Mylenie nazw pakietów PyJWT i python-jwt

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.

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

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

import jwt  # correct — this is PyJWT
print(jwt.__version__)  # 2.x
Pominięcie parametru algorithms

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.

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"])
Użycie jwt.decode() z nieprawidłowym typem klucza dla RS256

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.

Before · Python
After · Python
# This fails — RS256 needs a public key, not a string secret
payload = jwt.decode(token, "my-secret", algorithms=["RS256"])
# InvalidSignatureError
public_key = open("public_key.pem").read()
payload = jwt.decode(token, public_key, algorithms=["RS256"])
Zapomnienie o paddingu base64 przy ręcznym dekodowaniu

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

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 alternatywy — szybkie porównanie

Metoda
Weryfikuje podpis
Sprawdza claims
Własne typy
Wymaga instalacji
PyJWT jwt.decode()
✓ (exp, aud, iss, nbf)
N/A (zwraca dict)
pip install PyJWT
PyJWT bez weryfikacji
N/A
pip install PyJWT
Ręczne dekodowanie base64
N/A
Nie (biblioteka standardowa)
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 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.

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

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.

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

Python
import jwt
from datetime import timedelta

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

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.

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

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.

Python
import jwt

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

Powiązane narzędzia

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 SantosRecenzent techniczny

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.