JWT Decoder Python — декодування JWT з PyJWT

·DevOps Engineer & Python Automation Specialist·ПеревіреноMaria Santos·Опубліковано

Використовуйте безкоштовний JWT Decoder прямо в браузері — без встановлення.

Спробувати JWT Decoder онлайн →

Кожен API з токенною автентифікацією рано чи пізно передає вам JWT, і розібратися, що всередині нього — одне з тих завдань, що постійно виникають під час розробки. JWT декодер на Python перетворює непрозорий base64-рядок на читабельний словник claims, з яким можна реально працювати. Потрібний пакет PyPI — це PyJWT — встановлюється командою pip install PyJWT, але імпортується як import jwt. Цей посібник охоплює jwt.decode() з повною перевіркою підпису, декодування без секрету для швидкої інспекції, ручне декодування base64 без жодних бібліотек, верифікацію відкритого ключа RS256 та типові помилки, з якими я стикався у продакшн auth-системах. Для швидкої одноразової перевірки скористайтесь онлайн JWT Decoder — він робить це миттєво без жодного коду. Усі приклади розраховані на Python 3.10+ та PyJWT 2.x.

  • pip install PyJWT, потім import jwt — назва пакета та назва імпорту різні, що збиває з пантелику майже всіх.
  • jwt.decode(token, key, algorithms=["HS256"]) повертає звичайний dict з claims. Завжди явно вказуйте algorithms.
  • Для інспекції claims без верифікації: jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
  • Для RSA/EC токенів: pip install PyJWT cryptography — бекенд cryptography є обов'язковим для асиметричних алгоритмів.
  • Ручне декодування (base64 + json) працює без жодних бібліотек, але пропускає всі перевірки підпису та терміну дії.

Що таке декодування JWT?

JSON Web Token — це три сегменти, закодовані у форматі base64url та розділені крапками: заголовок (алгоритм і тип токена), payload (claims — ідентифікатор користувача, ролі, час закінчення дії) та підпис. Декодування JWT означає вилучення сегментів заголовка і payload, їх декодування з base64url і розбір результуючого JSON у словник claims.

Заголовок повідомляє, який алгоритм використовувався для підпису токена, і іноді містить kid (ідентифікатор ключа) для пошуку потрібного ключа верифікації. Payload містить фактичні дані: кому виданий токен (sub), коли він закінчується (exp), для якого сервісу призначений (aud), а також будь-які власні claims, визначені у вашому застосунку. Сегмент підпису підтверджує, що токен не було підроблено, але для його верифікації потрібен секретний або відкритий ключ. Декодування і верифікація — це окремі операції. Ви можете декодувати payload без верифікації підпису (корисно для налагодження), але ніколи не довіряйте неперевіреним claims при прийнятті рішень щодо авторизації.

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

jwt.decode() — декодування та верифікація з PyJWT

jwt.decode() — основна функція бібліотеки PyJWT. Вона приймає закодований рядок токена, секретний ключ (для HMAC-алгоритмів) або відкритий ключ (для RSA/EC), а також обов'язковий список algorithms. Функція перевіряє підпис, валідує стандартні claims на кшталт exp та nbfі повертає payload у вигляді словника Python. Якщо щось не так — поганий підпис, прострочений токен, неправильний алгоритм — генерується відповідний виняток.

Мінімальний робочий приклад

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

Параметр algorithms є списком, а не одним рядком, і в PyJWT 2.x він є обов'язковим. Це захисна функція: без нього зловмисник може створити токен із alg: none у заголовку та повністю обійти верифікацію. Завжди вказуйте саме ті алгоритми, які приймає ваш застосунок. Якщо ви видаєте лише HS256-токени, список має бути ["HS256"] — а не ["HS256", "RS256", "none"]. Короткий список зменшує поверхню атаки.

Одна річ, яка спантеличила мене на початку: у PyJWT 2.x функція jwt.encode() змінила тип повернення з bytes на рядок. Якщо ви читаєте старі відповіді на Stack Overflow, де викликається .decode("utf-8") на закодованому токені, цей код написаний для PyJWT 1.x і генеруватиме AttributeError у версії 2.x. Токен вже є рядком — просто використовуйте його напряму.

Повний цикл з терміном дії

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")
Примітка:PyJWT автоматично конвертує об'єкти datetime у Unix-мітки часу під час кодування. При декодуванні claims exp, iat та nbf повертаються як цілі числа, а не об'єкти datetime. Перетворення треба виконати самостійно за допомогою datetime.fromtimestamp(payload["exp"], tz=timezone.utc).

Декодування JWT без верифікації підпису

Іноді потрібно прочитати claims до того, як ви зможете верифікувати токен. Типовий сценарій: заголовок токена містить поле kid (ідентифікатор ключа), і вам спочатку потрібно отримати відповідний відкритий ключ із JWKS-ендпоінту. PyJWT підтримує це через опцію verify_signature: False. Список algorithms все одно передається, але аргумент key ігнорується.

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
Попередження:Неперевіреним токенам не можна довіряти. Використовуйте цей підхід лише для маршрутизаційних рішень (який ключ отримати, якого тенанта знайти). Ніколи не приймайте рішення щодо авторизації на основі неперевірених claims. Зловмисник може вписати будь-що у payload.

Тут є тонке розмежування. jwt.get_unverified_header() читає лише заголовок — перший сегмент. Виклик jwt.decode() з verify_signature: False читає payload (другий сегмент). Разом вони дозволяють витягти все з токена без ключа. PyJWT все одно перевіряє правильність структури токена (три сегменти, розділені крапками, коректний base64, коректний JSON) навіть при вимкненій верифікації підпису. Якщо токен структурно пошкоджений, буде згенеровано DecodeError незалежно від переданих параметрів.

Довідник параметрів jwt.decode()

Повний підпис: jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require). Усі параметри після algorithms є keyword-only.

Параметр
Тип
За замовчуванням
Опис
jwt
str | bytes
(обов'язковий)
Закодований рядок JWT для декодування
key
str | bytes | dict
(обов'язковий)
Секретний ключ (HMAC) або відкритий ключ (RSA/EC) для перевірки
algorithms
list[str]
(обов'язковий)
Дозволені алгоритми — наприклад ["HS256"], ["RS256"]. Ніколи не пропускайте цей параметр.
options
dict
{}
Перевизначення прапорів перевірки: verify_signature, verify_exp, verify_aud тощо.
audience
str | list[str]
None
Очікуване значення claim aud — генерує InvalidAudienceError при невідповідності
issuer
str
None
Очікуване значення claim iss — генерує InvalidIssuerError при невідповідності
leeway
timedelta | int
0
Допустиме відхилення годинника в секундах для перевірок exp та nbf
require
list[str]
[]
Claims, які мають бути присутні — генерує MissingRequiredClaimError у разі їх відсутності

Словник options дає тонкий контроль над тим, які перевірки виконує PyJWT. Ключі відповідають окремим перевіркам: verify_signature, verify_exp, verify_nbf, verify_iss, verify_audта verify_iat. Усі вони за замовчуванням мають значення True, якщо їх явно не встановлено в False. У продакшені залишайте всі ці значення за замовчуванням. Вимикати окремі перевірки я дозволяю собі лише під час розробки, коли працюю зі старими тестовими токенами і тимчасово потрібно обійти перевірку терміну дії.

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
)

Ручне декодування JWT за допомогою base64 та json

Ви можете декодувати JWT payload, використовуючи лише стандартну бібліотеку Python — без pip install. Це справді корисно в кількох ситуаціях: налагоджувальні скрипти, де додавати залежність надлишково; обмежені CI-середовища, де доступна лише стандартна бібліотека; AWS Lambda функції, де потрібно мінімізувати час холодного старту; або просто для розуміння того, що насправді являє собою JWT. Процес простий: розбийте по крапках, візьміть потрібний сегмент, додайте base64 padding, декодуйте та розберіть JSON.

Модулі base64 та json входять до стандартної бібліотеки Python, тому цей підхід працює у будь-якій інсталяції Python 3.6+. Наведені нижче функції окремо обробляють заголовок (перший сегмент) та payload (другий сегмент):

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

Трюк з padding (+= "=" * (-len(s) % 4)) — це те, про що всі забувають. JWT base64url опускає завершальні символи =, але urlsafe_b64decode у Python вимагає їх. Без виправлення padding ви отримаєте binascii.Error: Incorrect padding.

Примітка:Ручне декодування не перевіряє підпис. Будь-хто може змінити payload JWT і перекодувати його. Використовуйте цей підхід лише для інспекції та налагодження, ніколи — для логіки авторизації. Для всього важливого використовуйте jwt.decode() з реальним ключем.

Декодування JWT з відповідей API та файлів токенів

Два найпоширеніші реальні сценарії: витягування JWT з HTTP-відповіді (OAuth token endpoint, login API) та читання токенів із файлів (облікові дані сервісних акаунтів, секрети Kubernetes, кешовані токени на диску). Обидва вимагають коректної обробки помилок. Мережеві запити зазнають збоїв. Файли зникають. Токени закінчуються між моментом кешування і моментом читання.

У наведених нижче прикладах використовується httpx для HTTP-запитів (замінюйте на requests за бажанням — підхід ідентичний) та pathlib.Path для файлових операцій. Кожен приклад перехоплює конкретні винятки PyJWT замість загального except Exception, щоб адекватно реагувати на кожен вид збою: повторна автентифікація при закінченні терміну дії, сповіщення при помилці підпису, повторна спроба при тайм-ауті мережі.

Декодування JWT з відповіді 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')}")

Декодування JWT з файлу

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 з командного рядка

Іноді просто потрібно швидко подивитись на токен у терміналі без написання скрипту. Можливо, ви налагоджуєте OAuth-потік і хочете побачити, що в заголовку Authorization, або скопіювали токен з DevTools браузера і хочете перевірити його термін дії. Прапорець -c Python перетворює це на однорядкову команду. Передайте токен через pipe і отримайте claims у форматі відформатованого JSON. Жодного файлу скрипту, жодного віртуального середовища.

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"

Для візуальної альтернативи без налаштування терміналу вставте ваш токен у JWT Decoder від ToolDeck і миттєво побачте заголовок, payload та статус верифікації підпису.

python-jose та інші альтернативи

python-jose — альтернативна JWT-бібліотека, що нативно підтримує JWS, JWE (зашифровані токени) та JWK. Якщо ваш застосунок має обробляти зашифровані JWT (JWE) — де сам payload зашифрований, а не просто підписаний — python-jose є правильним вибором, оскільки PyJWT взагалі не підтримує JWE. Бібліотека також має вбудовану підтримку наборів ключів JWKS, що спрощує інтеграцію з провайдерами ідентифікації на кшталт Auth0, Okta або Keycloak, які публікують ротаційні набори ключів. Інтерфейс декодування майже ідентичний PyJWT, тому перехід між ними потребує мінімальних змін коду:

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

Моя рекомендація: починайте з PyJWT. Він покриває 95% сценаріїв використання JWT, має найбільшу спільноту, і API у нього зрозумілий. Переходьте на python-jose, якщо потрібна підтримка JWE або зручніша робота з JWKS. Третій варіант, який варто згадати, — Authlib, що об'єднує обробку JWT у великому OAuth/OIDC фреймворку. Якщо ви вже використовуєте Authlib для OAuth-клієнтських потоків, його модуль authlib.jose.jwt позбавляє від необхідності додавати другу JWT-залежність. В іншому разі це важка залежність лише заради декодування токенів.

Виведення у термінал з підсвічуванням синтаксису

Читати сирі JWT claims у терміналі — прийнятно для швидких перевірок, але коли ви регулярно налагоджуєте payload токенів (я робив це щодня під час розробки внутрішнього auth-шлюзу у Харкові), кольорове виведення суттєво допомагає. Рядкові значення, числа, булеві значення та null відображаються різними кольорами, і ви одразу помічаєте відсутній дозвіл або неправильну мітку часу закінчення терміну дії, не перечитуючи кожен символ.

Бібліотека rich (pip install rich) має функцію print_json, яка приймає рядок JSON або словник Python і виводить його з повним підсвічуванням синтаксису у терміналі. Поєднайте її з PyJWT для дворядкового робочого процесу інспекції 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
# }
Примітка:Виведення rich містить ANSI-коди керування. Не записуйте його у файли і не повертайте з API-ендпоінтів — воно призначене лише для відображення у терміналі. Для звичайного текстового виводу використовуйте json.dumps().

Робота з великими партіями токенів

Самі JWT-токени невеликі (як правило, менше 2 КБ кожен), але бувають сценарії, коли їх потрібно обробляти масово. Аналіз журналів аудиту після інциденту безпеки. Скрипти міграції сесій при зміні auth-провайдерів. Пакетна перевірка відповідності вимогам, де потрібно підтвердити, що кожен токен, виданий за останні 90 днів, підписаний правильним ключем. Якщо у вас десятки тисяч токенів у NDJSON-файлі журналу, рядкова обробка дозволяє уникнути завантаження всього файлу в пам'ять і виводити результати поступово.

Пакетна валідація токенів із журналу аудиту

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 з 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")
Примітка:Для файлів розміром до кількох сотень МБ рядкового читання цілком достатньо. Якщо ви наткнетесь на обмеження продуктивності при обробці дуже великих дампів токенів, розгляньте використання multiprocessing.Pool для розподілу валідації між ядрами процесора, оскільки кожен токен є незалежним.

Типові помилки

Ці чотири помилки регулярно зустрічаються під час code review та в запитаннях на Stack Overflow. Кожна з них легко трапляється, і повідомлення про помилку від PyJWT не завжди вказує безпосередньо на причину. Лише проблема з назвою пакета, за моїми спостереженнями, забирала години налагодження, коли хтось встановлював не ту бібліотеку і отримував зовсім несподіваний API.

Плутанина між назвами пакетів PyJWT та python-jwt

Проблема: Виконання pip install jwt або pip install python-jwt встановлює зовсім інший пакет. Після цього import jwt або не спрацьовує, або дає API, якого ви не впізнаєте.

Вирішення: Завжди встановлюйте командою pip install PyJWT. Імпорт — import jwt. Перевіряйте встановлення командою pip show PyJWT.

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
Відсутність параметра algorithms

Проблема: У PyJWT 1.x параметр algorithms був необов'язковим і за замовчуванням дозволяв будь-який алгоритм. Це створювало вразливість, за якої зловмисник міг встановити alg: none. PyJWT 2.x тепер генерує DecodeError, якщо algorithms відсутній.

Вирішення: Завжди передавайте algorithms у вигляді явного списку. Використовуйте лише ті алгоритми, які ваш застосунок реально використовує для видачі токенів.

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() з неправильним типом ключа для RS256

Проблема: Передача рядкового секрету до jwt.decode() з algorithms=["RS256"] генерує InvalidSignatureError. RS256 вимагає PEM-закодований відкритий ключ, а не рядок спільного секрету.

Вирішення: Завантажуйте PEM відкритий ключ з файлу або змінної середовища. Встановіть пакет 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"])
Забутий padding при ручному декодуванні

Проблема: JWT base64url-кодування видаляє завершальні символи =. Python's base64.urlsafe_b64decode генерує binascii.Error: Incorrect padding, якщо передати сирий сегмент без виправлення padding.

Вирішення: Додайте padding перед декодуванням: segment += '=' * (-len(segment) % 4). Ця формула завжди дає правильну кількість символів padding (0, 1, 2 або 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 проти альтернатив — коротке порівняння

Метод
Перевіряє підпис
Валідує claims
Власні типи
Потребує встановлення
PyJWT jwt.decode()
✓ (exp, aud, iss, nbf)
Н/Д (повертає dict)
pip install PyJWT
PyJWT без верифікації
Н/Д
pip install PyJWT
Ручне декодування base64
Н/Д
Ні (stdlib)
python-jose jwt.decode()
Н/Д
pip install python-jose
Authlib jwt.decode()
Н/Д
pip install Authlib
PyJWT + cryptography
✓ (RSA/EC)
Н/Д
pip install PyJWT cryptography

PyJWT — правильна відправна точка для більшості Python-застосунків. Він охоплює HMAC та (з бекендом cryptography) RSA і EC верифікацію підпису. Якщо потрібна підтримка JWE (зашифровані токени), переходьте на python-jose або Authlib. Ручне base64-декодування підходить для налагодження, але не дає жодних гарантій безпеки.

Ось коли я обираю кожен варіант: PyJWT — для будь-якого стандартного вебсервісу з HS256 або RS256 верифікацією. python-jose — коли архітектура включає зашифровані токени або ротацію ключів JWKS. Ручне base64-декодування — для швидкої інспекції у середовищах, де pip недоступний (CI-контейнери, обмежені продакшн-хости, AWS Lambda де потрібно мінімізувати залежності). Authlib — коли проєкт вже використовує його для OAuth-клієнтських потоків і додавати ще одну JWT-бібліотеку надлишково.

Для варіанту без коду вставте будь-який токен у JWT Decoder і перегляньте декодований заголовок та payload з відповіддю щодо валідації claims.

Часті запитання

Як декодувати JWT у Python без перевірки підпису?

Передайте options={"verify_signature": False} та algorithms=["HS256"] у jwt.decode(). Це повертає словник payload без перевірки підпису. Використовуйте цей підхід лише для інспекції — читання claims перед отриманням потрібного відкритого ключа або налагодження під час розробки. Ніколи не пропускайте верифікацію для токенів, що контролюють доступ до будь-чого важливого.

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

У чому різниця між PyJWT та python-jwt?

PyJWT (pip install PyJWT, import jwt) — найпопулярніша JWT-бібліотека для Python з понад 80 мільйонами завантажень на місяць. python-jwt (pip install python_jwt) — це окрема, значно менш використовувана бібліотека з іншим API. Якщо ви бачите import jwt у чиємусь коді, вони використовують PyJWT. Плутанина виникає через те, що назва пакета PyPI (PyJWT) відрізняється від назви імпорту (jwt). Використовуйте PyJWT, якщо немає вагомих причин для іншого вибору.

Як декодувати JWT з RS256 у Python?

Встановіть PyJWT та бекенд cryptography: pip install PyJWT cryptography. Потім передайте PEM-закодований відкритий ключ як аргумент key та algorithms=["RS256"]. PyJWT делегує перевірку RSA-підпису бібліотеці cryptography. Без встановленого пакета cryptography PyJWT генерує помилку при спробі використати алгоритми RSA або EC.

Python
import jwt

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

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

Чому PyJWT генерує ExpiredSignatureError?

PyJWT за замовчуванням перевіряє claim exp (термін дії). Якщо поточний UTC-час перевищує мітку часу exp, генерується jwt.ExpiredSignatureError. Ви можете додати допуск на відхилення годинника за допомогою параметра leeway: jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Це дає 30-секундний запас. Щоб повністю вимкнути перевірку терміну дії (не рекомендується для продакшену), передайте 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")

Чи можна читати JWT claims без будь-якої бібліотеки у Python?

Так. Розбийте токен по крапках, візьміть другий сегмент (payload), доповніть його символами = до кратності 4, потім декодуйте base64url і розберіть JSON. Це дає словник claims, але не перевіряє підпис. Корисно в середовищах з обмеженнями, де не можна встановити PyJWT, або для швидких налагоджувальних скриптів.

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

Як валідувати claim audience за допомогою PyJWT?

Передайте параметр audience у jwt.decode(): jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). PyJWT порівнює claim aud у токені зі значенням, яке ви передаєте. Якщо токен не має claim aud або значення не збігається, генерується jwt.InvalidAudienceError. Ви також можете передати список допустимих audience, якщо ваш сервіс приймає токени для кількох API.

Python
import jwt

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

Пов'язані інструменти

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 SantosТехнічний рецензент

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.