JWT Decoder Python — Guia para decodificar JWT com PyJWT
Use o Decodificador JWT gratuito diretamente no seu navegador — sem instalação.
Experimentar Decodificador JWT online →Toda API que usa autenticação baseada em tokens entrega um JWT em algum momento, e descobrir o que há dentro dele é uma daquelas tarefas que surgem constantemente durante o desenvolvimento. Um decodificador de JWT em Python transforma aquela string base64 opaca em um dicionário de claims legível com o qual você pode realmente trabalhar. O pacote PyPI que você precisa é PyJWT — instalado com pip install PyJWT mas importado como import jwt. Este guia percorre jwt.decode() com verificação completa de assinatura, decodificação sem chave secreta para inspeção rápida, decodificação manual com base64 sem nenhuma biblioteca, verificação de chave pública RS256 e armadilhas comuns que encontrei em sistemas de autenticação em produção. Para uma verificação rápida pontual, o JWT Decoder online faz isso instantaneamente, sem nenhum código. Todos os exemplos são para Python 3.10+ e PyJWT 2.x.
- ✓pip install PyJWT, depois import jwt — o nome do pacote e o nome de importação são diferentes, o que confunde quase todo mundo.
- ✓jwt.decode(token, key, algorithms=["HS256"]) retorna um dict simples com os claims. Sempre passe algorithms explicitamente.
- ✓Para inspecionar claims sem verificação: jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
- ✓Para tokens RSA/EC: pip install PyJWT cryptography — o backend cryptography é necessário para algoritmos assimétricos.
- ✓A decodificação manual (base64 + json) funciona sem nenhuma biblioteca, mas ignora toda validação de assinatura e expiração.
O que é Decodificação de JWT?
Um JSON Web Token é formado por três segmentos codificados em base64url separados por pontos: um cabeçalho (algoritmo e tipo do token), um payload (os claims — ID do usuário, funções, tempo de expiração) e uma assinatura. Decodificar um JWT significa extrair os segmentos de cabeçalho e payload, decodificá-los com base64url e analisar o JSON resultante em um dicionário de claims.
O cabeçalho informa qual algoritmo foi usado para assinar o token e, às vezes, inclui um kid (ID da chave) para encontrar a chave de verificação correta. O payload contém os dados reais: para quem o token foi emitido (sub), quando ele expira (exp), para qual serviço ele se destina (aud), além de quaisquer claims personalizados que sua aplicação define. O segmento de assinatura prova que o token não foi adulterado, mas você precisa da chave secreta ou pública para verificá-lo. Decodificação e verificação são operações separadas. Você pode decodificar o payload sem verificar a assinatura (útil para depuração), mas nunca confie em claims não verificados para decisões de autorização.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
"sub": "usr_8f2a",
"role": "admin",
"exp": 1711815600
}jwt.decode() — Decodificar e Verificar com PyJWT
jwt.decode() é a função principal da biblioteca PyJWT. Ela recebe a string do token codificado, a chave secreta (para algoritmos HMAC) ou chave pública (para RSA/EC), e uma lista algorithms obrigatória. A função verifica a assinatura, confere claims padrão como exp e nbf, e retorna o payload como um dicionário Python. Se algo falhar — assinatura inválida, token expirado, algoritmo errado — ela lança uma exceção específica.
Exemplo Mínimo Funcional
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"])
# adminO parâmetro algorithms é uma lista, não uma string simples, e é obrigatório no PyJWT 2.x. Isso é uma medida de segurança: sem ele, um atacante poderia forjar um token com alg: none no cabeçalho e contornar a verificação por completo. Sempre especifique exatamente quais algoritmos sua aplicação aceita. Se você emite apenas tokens HS256, a lista deve ser ["HS256"] — não ["HS256", "RS256", "none"]. Manter a lista restrita reduz a superfície de ataque.
Uma coisa que me confundiu no início: o PyJWT 2.x alterou jwt.encode() para retornar uma string em vez de bytes. Se você estiver lendo respostas antigas do Stack Overflow que chamam .decode("utf-8") no token codificado, esse código é da era do PyJWT 1.x e vai lançar um AttributeError na versão 2.x. O token já é uma string — use-o diretamente.
Ciclo Completo com Expiração
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.interno.exemplo.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.interno.exemplo.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 em timestamps Unix automaticamente durante a codificação. Durante a decodificação, os claims exp, iat e nbf retornam como inteiros, não como objetos datetime. Você precisa convertê-los manualmente com datetime.fromtimestamp(payload["exp"], tz=timezone.utc).Decodificar um JWT Sem Verificação de Assinatura
Às vezes você precisa ler os claims antes de poder verificar o token. Um cenário comum: o cabeçalho do token contém um campo kid (ID da chave), e você precisa buscar a chave pública correspondente em um endpoint JWKS antes de verificar. O PyJWT suporta isso com a opção verify_signature: False. Você ainda passa a lista algorithms, mas o argumento key é ignorado.
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 endpointHá uma distinção sutil aqui. jwt.get_unverified_header() lê apenas o cabeçalho — o primeiro segmento. A chamada jwt.decode() com verify_signature: False lê o payload (segundo segmento). Com os dois, você pode extrair tudo de um token sem uma chave. O PyJWT ainda valida que o token tem a estrutura correta (três segmentos separados por pontos, base64 válido, JSON válido) mesmo quando a verificação de assinatura está desativada. Se o token estiver estruturalmente malformado, ele lança DecodeError independentemente das opções passadas.
Referência de Parâmetros do jwt.decode()
A assinatura completa é jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require). Todos os parâmetros após algorithms são apenas por palavra-chave.
O dict options oferece controle granular sobre quais validações o PyJWT realiza. As chaves mapeiam para verificações individuais: verify_signature, verify_exp, verify_nbf, verify_iss, verify_aud e verify_iat. Todos têm como padrão True a menos que você os defina explicitamente como False. Em produção, deixe todos nos seus valores padrão. O único momento em que desativo verificações individuais é durante o desenvolvimento, quando estou trabalhando com tokens de teste antigos e preciso ignorar a expiração temporariamente.
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.interno.exemplo.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
)Decodificação Manual de JWT com base64 e json
Você pode decodificar um payload JWT usando apenas a biblioteca padrão do Python — sem pip install necessário. Isso é genuinamente útil em diversas situações: scripts de depuração onde adicionar uma dependência é desnecessário, ambientes de CI restritos onde apenas a biblioteca padrão está disponível, funções AWS Lambda onde você quer minimizar o tempo de cold start, ou simplesmente para entender o que um JWT realmente é por baixo dos panos. O processo é direto: divida nos pontos, pegue o segmento desejado, adicione o padding de base64, decodifique e analise o JSON.
Os módulos base64 e json fazem parte da biblioteca padrão do Python, então essa abordagem funciona em qualquer instalação Python a partir da versão 3.6. As funções abaixo lidam com o cabeçalho (primeiro segmento) e o payload (segundo segmento) separadamente:
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: adminO truque do padding (+= "=" * (-len(s) % 4)) é a parte que todos esquecem. O base64url do JWT omite os caracteres = de preenchimento no final, mas o urlsafe_b64decode do Python os exige. Sem a correção do padding, você recebe um binascii.Error: Incorrect padding.
jwt.decode() com uma chave real.Decodificar JWTs de Respostas de API e Arquivos de Token
Os dois cenários mais comuns no mundo real: extrair um JWT de uma resposta HTTP (um endpoint de token OAuth, uma API de login) e ler tokens de arquivos (credenciais de conta de serviço, secrets montados no Kubernetes, tokens em cache no disco). Ambos precisam de tratamento adequado de erros. Requisições de rede falham. Arquivos somem. Tokens expiram entre quando foram armazenados em cache e quando são lidos.
Os exemplos abaixo usam httpx para chamadas HTTP (substitua por requests se preferir, o padrão é idêntico) e pathlib.Path para operações com arquivos. Cada exemplo captura exceções específicas do PyJWT em vez de um genérico except Exception, para que você possa responder adequadamente a cada modo de falha: reautenticar na expiração, alertar em caso de falha de assinatura, tentar novamente em timeout de rede.
Decodificar um JWT de uma Resposta de 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')}")Decodificar um JWT de um Arquivo
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']}")Decodificação de JWT pela Linha de Comando
Às vezes você só precisa inspecionar um token pelo terminal sem escrever um script. Talvez esteja depurando um fluxo OAuth e queira ver o que há no cabeçalho Authorization, ou pegou um token do DevTools do navegador e quer verificar sua expiração. A flag -c do Python torna isso uma instrução de uma linha. Passe o token e obtenha os claims como JSON formatado. Sem arquivo de script, sem ambiente virtual.
# 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"Para uma alternativa visual sem nenhuma configuração de terminal, cole seu token no JWT Decoder do ToolDeck e veja o cabeçalho, o payload e o status de verificação da assinatura instantaneamente.
python-jose e Outras Alternativas
python-jose é uma biblioteca JWT alternativa que suporta JWS, JWE (tokens criptografados) e JWK nativamente. Se sua aplicação precisa lidar com JWTs criptografados (JWE) — onde o próprio payload é criptografado, não apenas assinado — o python-jose é a escolha certa, pois o PyJWT não suporta JWE de forma alguma. A biblioteca também tem suporte nativo a conjuntos de chaves JWKS, o que simplifica a integração com provedores de identidade como Auth0, Okta ou Keycloak que expõem conjuntos de chaves rotativas. A interface de decodificação é quase idêntica ao PyJWT, então trocar entre eles requer alterações mínimas no código:
# 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']}")Minha recomendação: comece com PyJWT. Ele cobre 95% dos casos de uso de JWT, tem a maior comunidade e a API é limpa. Mude para python-jose se precisar de suporte a JWE ou preferir o seu tratamento de JWKS. Uma terceira opção que vale mencionar é Authlib, que inclui o tratamento de JWT dentro de um framework OAuth/OIDC muito maior. Se você já usa Authlib para fluxos de cliente OAuth, o seu módulo authlib.jose.jwt evita adicionar uma segunda dependência JWT. Caso contrário, é uma dependência pesada apenas para decodificação de tokens.
Saída no Terminal com Realce de Sintaxe
Ler claims JWT brutos no terminal é suficiente para verificações rápidas, mas quando você depura payloads de tokens regularmente (fiz isso diariamente enquanto construía um gateway de autenticação interno), a saída colorida faz uma diferença real. Strings, números, booleanos e null renderizam em cores distintas, o que significa que você pode identificar uma permissão ausente ou um timestamp de expiração incorreto de relance, sem ler cada caractere.
A biblioteca rich (pip install rich) tem uma função print_json que recebe uma string JSON ou um dict Python e imprime com realce de sintaxe completo no terminal. Combine-a com PyJWT para um fluxo de inspeção de JWT em duas linhas:
# 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 contém códigos de escape ANSI. Não a escreva em arquivos nem a retorne de endpoints de API — ela é apenas para exibição no terminal. Use json.dumps() quando precisar de saída em texto simples.Trabalhando com Grandes Lotes de Tokens
Os tokens JWT em si são pequenos (tipicamente menos de 2 KB cada), mas há cenários em que você os processa em lote. Análise de logs de auditoria após um incidente de segurança. Scripts de migração de sessão ao trocar provedores de autenticação. Validação de lote por conformidade onde você precisa provar que cada token emitido nos últimos 90 dias foi assinado com a chave correta. Se você tiver dezenas de milhares de tokens em um arquivo de log NDJSON, processá-los linha a linha evita carregar o arquivo inteiro na memória e permite que você reporte resultados de forma incremental.
Validar Tokens em Lote de um Log de Auditoria
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: 17Extrair Claims de um Export NDJSON de Tokens
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 para distribuir a validação entre os núcleos, já que cada token é independente.Erros Comuns
Esses quatro erros aparecem repetidamente em revisões de código e perguntas no Stack Overflow. Cada um é fácil de cometer, e a mensagem de erro que o PyJWT fornece nem sempre aponta diretamente para a causa. Já vi o problema com o nome do pacote sozinho desperdiçar horas de depuração quando alguém instala a biblioteca errada e obtém uma API completamente inesperada.
Problema: Executar pip install jwt ou pip install python-jwt instala um pacote completamente diferente. import jwt então falha ou fornece uma API que você não reconhece.
Solução: Sempre instale com pip install PyJWT. O import é import jwt. Verifique com pip show PyJWT para confirmar que o pacote correto está instalado.
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
Problema: No PyJWT 1.x, algorithms era opcional e aceitava qualquer algoritmo por padrão. Isso criava uma vulnerabilidade de segurança onde um atacante podia definir alg: none. O PyJWT 2.x agora lança DecodeError se algorithms estiver ausente.
Solução: Sempre passe algorithms como uma lista explícita. Use apenas os algoritmos com os quais sua aplicação realmente emite tokens.
# 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"])
Problema: Passar uma string secreta para jwt.decode() com algorithms=["RS256"] lança InvalidSignatureError. RS256 requer uma chave pública codificada em PEM, não uma string de segredo compartilhado.
Solução: Carregue a chave pública PEM de um arquivo ou variável de ambiente. Instale o pacote 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"])Problema: A codificação base64url do JWT remove os caracteres = de preenchimento no final. O base64.urlsafe_b64decode do Python lança binascii.Error: Incorrect padding se você passar o segmento bruto sem corrigir o padding.
Solução: Adicione padding antes de decodificar: segment += '=' * (-len(segment) % 4). Essa fórmula sempre produz o número correto de caracteres de padding (0, 1, 2 ou 3).
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect paddingimport base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64) # worksPyJWT vs Alternativas — Comparação Rápida
PyJWT é o ponto de partida certo para a maioria das aplicações Python. Cobre HMAC e (com o backend cryptography) verificação de assinatura RSA e EC. Se você precisar de JWE (tokens criptografados), mude para python-jose ou Authlib. A decodificação manual com base64 funciona para depuração, mas não oferece nenhuma garantia de segurança.
Aqui está quando uso cada opção: PyJWT para qualquer serviço web padrão que faça verificação HS256 ou RS256. python-jose quando a arquitetura inclui tokens criptografados ou rotação de JWKS. Base64 manual para inspeção rápida em ambientes onde pip não está disponível (containers de CI, hosts de produção restritos, cold starts de AWS Lambda onde você quer minimizar dependências). Authlib quando o projeto já o usa para fluxos de cliente OAuth e adicionar outra biblioteca JWT seria redundante.
Para uma alternativa sem código, cole qualquer token no JWT Decoder para ver o cabeçalho e o payload decodificados com feedback de validação dos claims.
Perguntas Frequentes
Como decodifico um JWT em Python sem verificar a assinatura?
Passe options={"verify_signature": False} e algorithms=["HS256"] para jwt.decode(). Isso retorna o dict do payload sem verificar a assinatura. Use apenas para inspeção — ler claims antes de buscar a chave pública correta, ou para depuração em desenvolvimento. Nunca pule a verificação em tokens que controlam acesso a qualquer recurso.
import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
payload = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}Qual é a diferença entre PyJWT e python-jwt?
PyJWT (pip install PyJWT, import jwt) é a biblioteca JWT mais popular para Python, com mais de 80 milhões de downloads mensais. python-jwt (pip install python_jwt) é uma biblioteca separada, muito menos utilizada, com uma API diferente. Se você vir import jwt no código de alguém, essa pessoa está usando PyJWT. A confusão de nomes vem do fato de que o pacote no PyPI se chama PyJWT, mas o nome de importação é jwt. Fique com PyJWT, a menos que tenha um motivo específico para não usá-lo.
Como decodifico um JWT com RS256 em Python?
Instale tanto PyJWT quanto o backend cryptography: pip install PyJWT cryptography. Em seguida, passe a chave pública codificada em PEM como argumento key e algorithms=["RS256"]. O PyJWT delega a verificação de assinatura RSA para a biblioteca cryptography. Sem o pacote cryptography instalado, o PyJWT lança um erro ao tentar usar algoritmos RSA ou EC.
import jwt
public_key = open("public_key.pem").read()
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience="https://api.example.com"
)Por que o PyJWT lança ExpiredSignatureError?
O PyJWT verifica o claim exp (expiração) por padrão. Se o horário UTC atual for posterior ao timestamp de exp, ele lança jwt.ExpiredSignatureError. Você pode adicionar tolerância para desvio de relógio com o parâmetro leeway: jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Isso dá um período de graça de 30 segundos. Para desativar completamente a verificação de expiração (não recomendado em produção), passe 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")Posso ler claims de JWT sem nenhuma biblioteca em Python?
Sim. Divida o token nos pontos, pegue o segundo segmento (o payload), adicione caracteres = para que o comprimento seja múltiplo de 4, depois decodifique com base64url e analise o JSON. Isso fornece o dict de claims, mas não verifica a assinatura. É útil em ambientes restritos onde não é possível instalar o PyJWT, ou para scripts de depuração rápida.
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'}Como valido o claim de audience com PyJWT?
Passe o parâmetro audience para jwt.decode(): jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). O PyJWT compara o claim aud no token com o valor que você fornece. Se o token não tiver claim aud, ou o valor não coincidir, ele lança jwt.InvalidAudienceError. Você também pode passar uma lista de audiences aceitáveis se seu serviço aceitar tokens destinados a múltiplas APIs.
import jwt
payload = jwt.decode(
token,
secret,
algorithms=["HS256"],
audience=["https://api.example.com", "https://admin.example.com"]
)Ferramentas Relacionadas
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.