JWT Decoder Python — декодирование JWT с PyJWT
Используйте бесплатный JWT Decoder прямо в браузере — установка не требуется.
Попробовать JWT Decoder онлайн →Любой API с токенной аутентификацией рано или поздно возвращает вам JWT, и разобраться в его содержимом — одна из задач, которая возникает постоянно в процессе разработки. Декодер JWT на Python превращает непрозрачную base64-строку в читаемый словарь полей, с которым можно работать. Нужный вам пакет в PyPI — PyJWT — устанавливается через pip install PyJWT, но импортируется как import jwt. В этом руководстве рассматривается jwt.decode() с полной верификацией подписи, декодирование без секрета для быстрой инспекции, ручное base64-декодирование без сторонних библиотек, верификация по публичному ключу RS256, а также типичные ошибки, с которыми я сталкивался в продакшен-системах аутентификации. Для разового быстрого просмотра токена воспользуйтесь онлайн-декодером JWT — он показывает результат мгновенно без написания кода. Все примеры рассчитаны на Python 3.10+ и PyJWT 2.x.
- ✓pip install PyJWT, затем import jwt — имя пакета и имя при импорте различаются, что сбивает с толку почти всех.
- ✓jwt.decode(token, key, algorithms=["HS256"]) возвращает обычный dict с полями. Всегда передавайте algorithms явно.
- ✓Для инспекции без верификации: jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
- ✓Для токенов RSA/EC: pip install PyJWT cryptography — криптографический бэкенд обязателен для асимметричных алгоритмов.
- ✓Ручное декодирование (base64 + json) работает без библиотек, но не выполняет верификацию подписи и срока действия.
Что такое декодирование JWT?
JSON Web Token состоит из трёх сегментов в кодировке base64url, разделённых точками: заголовок (алгоритм и тип токена), payload (поля — ID пользователя, роли, время истечения) и подпись. Декодирование JWT означает извлечение сегментов заголовка и payload, их base64url-декодирование и разбор результирующего JSON в словарь полей.
Заголовок указывает, какой алгоритм использовался для подписи токена, и иногда содержит kid (идентификатор ключа) для выбора нужного ключа верификации. Payload содержит сами данные: кому выдан токен (sub), когда истекает (exp), для какого сервиса предназначен (aud), а также любые дополнительные поля вашего приложения. Сегмент подписи подтверждает, что токен не был изменён, но для её верификации нужен секретный или публичный ключ. Декодирование и верификация — это разные операции. Payload можно декодировать без проверки подписи (удобно для отладки), но никогда не доверяйте непроверенным полям при принятии решений об авторизации.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
"sub": "usr_8f2a",
"role": "admin",
"exp": 1711815600
}jwt.decode() — декодирование и верификация с PyJWT
jwt.decode() — основная функция библиотеки PyJWT. Она принимает закодированную строку токена, секретный ключ (для алгоритмов HMAC) или публичный ключ (для RSA/EC), а также обязательный список algorithms. Функция верифицирует подпись, проверяет стандартные поля — exp и nbf — и возвращает payload в виде словаря Python. При любой ошибке — неверная подпись, истёкший токен, неправильный алгоритм — вызывается соответствующее исключение.
Минимальный рабочий пример
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() — теперь она возвращает строку вместо байтов. Если вы находите старые ответы на Stack Overflow, где после закодированного токена вызывается .decode("utf-8"), это код эпохи PyJWT 1.x, который выбросит AttributeError на версии 2.x. Токен уже является строкой — используйте его напрямую.
Полный цикл с временем истечения
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 в Unix-метки времени при кодировании. При декодировании поля exp, iat и nbf возвращаются как целые числа, а не объекты datetime. Для конвертации используйте datetime.fromtimestamp(payload["exp"], tz=timezone.utc).Декодирование JWT без верификации подписи
Иногда нужно прочитать поля токена до его верификации. Типичный сценарий: в заголовке токена содержится поле kid (идентификатор ключа), и перед верификацией нужно получить нужный публичный ключ с JWKS-эндпоинта. PyJWT поддерживает это через опцию verify_signature: False. Список algorithms всё равно передаётся, но аргумент key игнорируется.
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Здесь важно различать два метода. 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).
Словарь options даёт точечный контроль над тем, какие проверки выполняет PyJWT. Ключи соответствуют отдельным проверкам: verify_signature, verify_exp, verify_nbf, verify_iss, verify_aud и verify_iat. По умолчанию все они равны True, если явно не установлены в False. В продакшене оставляйте все значения по умолчанию. Отключать отдельные проверки имеет смысл только в процессе разработки, когда нужно работать с устаревшими тестовыми токенами и временно пропустить проверку срока действия.
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
Payload JWT можно декодировать, используя только стандартную библиотеку Python — без pip install. Это действительно полезно в нескольких ситуациях: отладочные скрипты, где добавлять зависимость избыточно; ограниченные CI-окружения, где доступна только стандартная библиотека; функции AWS Lambda, где нужно минимизировать время холодного старта; или просто понимание того, что JWT представляет собой на низком уровне. Процесс прост: разделить по точкам, взять нужный сегмент, добавить padding для base64, декодировать и разобрать JSON.
Модули base64 и json входят в стандартную библиотеку Python, поэтому этот подход работает на любой установке Python начиная с версии 3.6. Функции ниже обрабатывают заголовок (первый сегмент) и payload (второй сегмент) раздельно:
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 опускает завершающие символы =, но Python's urlsafe_b64decode требует их наличия. Без исправления padding вы получите binascii.Error: Incorrect padding.
jwt.decode() с реальным ключом.Декодирование JWT из API-ответов и файлов с токенами
Два наиболее частых сценария: извлечение JWT из HTTP-ответа (эндпоинт OAuth-токена, API входа в систему) и чтение токенов из файлов (учётные данные сервисных аккаунтов, смонтированные секреты Kubernetes, кэшированные токены на диске). Оба требуют корректной обработки ошибок. Сетевые запросы завершаются неудачей. Файлы исчезают. Токены истекают между моментом кэширования и чтения.
В примерах ниже используется httpx для HTTP-вызовов (замените на requests при желании — паттерн идентичен) и pathlib.Path для файловых операций. Каждый пример перехватывает конкретные исключения PyJWT, а не общий except Exception, чтобы правильно реагировать на каждый тип сбоя: повторная аутентификация при истечении, оповещение при ошибке подписи, повтор запроса при сетевом таймауте.
Декодирование JWT из 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')}")Декодирование JWT из файла
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 и получите поля в формате JSON. Ни файл скрипта, ни виртуальное окружение не нужны.
# 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"Если нужен визуальный вариант без работы в терминале, вставьте токен в декодер JWT на ToolDeck — заголовок, payload и статус верификации подписи отображаются мгновенно.
python-jose и другие альтернативы
python-jose — альтернативная JWT-библиотека с нативной поддержкой JWS, JWE (зашифрованных токенов) и JWK. Если ваше приложение должно работать с зашифрованными JWT (JWE) — где сам payload зашифрован, а не только подписан — python-jose является правильным выбором, поскольку PyJWT не поддерживает JWE вообще. Библиотека также имеет встроенную обработку наборов ключей JWKS, что упрощает интеграцию с провайдерами идентификации — Auth0, Okta или Keycloak — публикующими ротируемые наборы ключей. Интерфейс декодирования практически идентичен PyJWT, поэтому переход между ними требует минимальных изменений кода:
# 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 в терминале вполне приемлемо для быстрых проверок, но когда вы регулярно анализируете payload токенов (я делал это ежедневно при разработке внутреннего auth-шлюза), цветной вывод действительно помогает. Строки, числа, булевы значения и null отображаются разными цветами, поэтому пропущенное разрешение или неправильная временная метка видны сразу, без чтения каждого символа.
Библиотека rich (pip install rich) содержит функцию print_json, которая принимает JSON-строку или словарь Python и выводит его с полной подсветкой синтаксиса в терминале. Совместите её с PyJWT и получите двухстрочный инструмент для инспекции 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 содержит ANSI escape-коды. Не записывайте его в файлы и не возвращайте из API-эндпоинтов — он предназначен только для отображения в терминале. Для текстового вывода используйте json.dumps().Работа с большими наборами токенов
Сами JWT-токены небольшие (как правило, менее 2 КБ), но бывают сценарии, когда их нужно обрабатывать пакетами. Анализ журнала аудита после инцидента безопасности. Скрипты миграции сессий при смене провайдера аутентификации. Массовая проверка соответствия требованиям, когда нужно подтвердить, что каждый токен, выпущенный за последние 90 дней, подписан правильным ключом. Если у вас десятки тысяч токенов в NDJSON-файле журнала, построчная обработка позволяет не загружать весь файл в память и выводить результаты постепенно.
Пакетная валидация токенов из журнала аудита
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Извлечение полей из 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 для распределения валидации по ядрам — каждый токен независим.Распространённые ошибки
Эти четыре ошибки постоянно встречаются в код-ревью и вопросах на Stack Overflow. Каждую из них легко допустить, и сообщение об ошибке от PyJWT не всегда указывает на причину напрямую. Путаницу с именами пакетов я наблюдал сам — она тратит часы отладочного времени, когда разработчик устанавливает не ту библиотеку и получает совершенно неожиданный API.
Проблема: pip install jwt или pip install python-jwt устанавливает совершенно другой пакет. import jwt тогда либо не работает, либо даёт API, которого вы не узнаёте.
Решение: Всегда устанавливайте через pip install PyJWT. Импорт — import jwt. Проверьте правильность пакета командой pip show PyJWT.
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
Проблема: В PyJWT 1.x параметр algorithms был необязательным и по умолчанию разрешал любой алгоритм. Это создавало уязвимость: злоумышленник мог выставить alg: none. PyJWT 2.x теперь вызывает DecodeError, если algorithms не передан.
Решение: Всегда передавайте algorithms как явный список. Используйте только те алгоритмы, которые ваше приложение действительно применяет для выпуска токенов.
# 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() с algorithms=["RS256"] вызывает InvalidSignatureError. RS256 требует публичный ключ в формате PEM, а не строку с общим секретом.
Решение: Загрузите PEM-публичный ключ из файла или переменной окружения. Установите пакет 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"])Проблема: JWT base64url опускает завершающие символы =. Python's base64.urlsafe_b64decode вызывает binascii.Error: Incorrect padding, если передать сегмент без исправления padding.
Решение: Добавьте padding перед декодированием: segment += '=' * (-len(segment) % 4). Эта формула всегда даёт правильное количество символов padding (0, 1, 2 или 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 против альтернатив — быстрое сравнение
PyJWT — правильная отправная точка для большинства Python-приложений. Он покрывает HMAC и (с криптографическим бэкендом) верификацию подписей RSA и EC. Если нужна поддержка JWE (зашифрованных токенов), переходите на python-jose или Authlib. Ручное base64-декодирование работает для отладки, но не даёт никаких гарантий безопасности.
Когда я выбираю каждый вариант: PyJWT — для любого стандартного веб-сервиса с верификацией HS256 или RS256. python-jose — когда архитектура предполагает зашифрованные токены или ротацию ключей JWKS. Ручной base64 — для быстрой инспекции в окружениях, где pip недоступен (CI-контейнеры, ограниченные хосты в продакшене, AWS Lambda с минимизацией зависимостей). Authlib — когда проект уже использует его для OAuth-потоков и добавлять ещё одну JWT-библиотеку избыточно.
Для варианта без кода вставьте любой токен в декодер JWT — декодированный заголовок и payload с обратной связью по валидации полей отображаются мгновенно.
Часто задаваемые вопросы
Как декодировать JWT в Python без проверки подписи?
Передайте options={"verify_signature": False} и algorithms=["HS256"] в jwt.decode(). Функция вернёт словарь с полями, не проверяя подпись. Используйте это только для инспекции — чтения полей перед получением нужного публичного ключа или при отладке в процессе разработки. Никогда не пропускайте верификацию для токенов, которые управляют доступом к чему-либо.
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 и криптографический бэкенд: pip install PyJWT cryptography. Затем передайте публичный ключ в формате PEM в качестве аргумента key и algorithms=["RS256"]. PyJWT делегирует верификацию RSA-подписи библиотеке cryptography. Без установленного пакета cryptography PyJWT выдаёт ошибку при попытке использовать алгоритмы RSA или EC.
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 по умолчанию проверяет поле exp (время истечения). Если текущее UTC-время превышает значение exp, библиотека вызывает jwt.ExpiredSignatureError. Допустимое отклонение часов можно задать через параметр leeway: jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Это даёт 30 секунд запаса. Чтобы полностью отключить проверку истечения срока (не рекомендуется в продакшене), передайте 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")Можно ли читать поля JWT без сторонних библиотек в Python?
Да. Разделите токен по точкам, возьмите второй сегмент (payload), добавьте символы = для кратности длины четырём, затем выполните base64url-декодирование и разберите JSON. В результате получите словарь с полями, но без верификации подписи. Это удобно в ограниченных окружениях, где нельзя установить PyJWT, или для быстрых отладочных скриптов.
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'}Как валидировать поле audience с помощью PyJWT?
Передайте параметр audience в jwt.decode(): jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). PyJWT сравнивает поле aud в токене с переданным значением. Если токен не содержит поле aud или значение не совпадает, вызывается jwt.InvalidAudienceError. Можно также передать список допустимых аудиторий, если сервис принимает токены для нескольких API.
import jwt
payload = jwt.decode(
token,
secret,
algorithms=["HS256"],
audience=["https://api.example.com", "https://admin.example.com"]
)Связанные инструменты
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.