JWT Decoder Python — декодування JWT з PyJWT
Використовуйте безкоштовний 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 при прийнятті рішень щодо авторизації.
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. Якщо щось не так — поганий підпис, прострочений токен, неправильний алгоритм — генерується відповідний виняток.
Мінімальний робочий приклад
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. Токен вже є рядком — просто використовуйте його напряму.
Повний цикл з терміном дії
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-мітки часу під час кодування. При декодуванні claims exp, iat та nbf повертаються як цілі числа, а не об'єкти datetime. Перетворення треба виконати самостійно за допомогою datetime.fromtimestamp(payload["exp"], tz=timezone.utc).Декодування JWT без верифікації підпису
Іноді потрібно прочитати claims до того, як ви зможете верифікувати токен. Типовий сценарій: заголовок токена містить поле 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
Ви можете декодувати JWT payload, використовуючи лише стандартну бібліотеку Python — без pip install. Це справді корисно в кількох ситуаціях: налагоджувальні скрипти, де додавати залежність надлишково; обмежені CI-середовища, де доступна лише стандартна бібліотека; AWS Lambda функції, де потрібно мінімізувати час холодного старту; або просто для розуміння того, що насправді являє собою JWT. Процес простий: розбийте по крапках, візьміть потрібний сегмент, додайте base64 padding, декодуйте та розберіть 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 опускає завершальні символи =, але urlsafe_b64decode у Python вимагає їх. Без виправлення padding ви отримаєте binascii.Error: Incorrect padding.
jwt.decode() з реальним ключем.Декодування JWT з відповідей API та файлів токенів
Два найпоширеніші реальні сценарії: витягування JWT з HTTP-відповіді (OAuth token endpoint, login 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 і отримайте claims у форматі відформатованого 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 Decoder від 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 claims у терміналі — прийнятно для швидких перевірок, але коли ви регулярно налагоджуєте 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-коди керування. Не записуйте його у файли і не повертайте з API-ендпоінтів — воно призначене лише для відображення у терміналі. Для звичайного текстового виводу використовуйте json.dumps().Робота з великими партіями токенів
Самі JWT-токени невеликі (як правило, менше 2 КБ кожен), але бувають сценарії, коли їх потрібно обробляти масово. Аналіз журналів аудиту після інциденту безпеки. Скрипти міграції сесій при зміні auth-провайдерів. Пакетна перевірка відповідності вимогам, де потрібно підтвердити, що кожен токен, виданий за останні 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Витягування claims з 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 для розподілу валідації між ядрами процесора, оскільки кожен токен є незалежним.Типові помилки
Ці чотири помилки регулярно зустрічаються під час code review та в запитаннях на 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 та (з бекендом 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 перед отриманням потрібного відкритого ключа або налагодження під час розробки. Ніколи не пропускайте верифікацію для токенів, що контролюють доступ до будь-чого важливого.
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.
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}.
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, або для швидких налагоджувальних скриптів.
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.
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.