Decodificador de JWT: lee el token, comprueba la caducidad y verifica la firma

Pega un JSON Web Token (JWT) y la herramienta lo decodifica: muestra la cabecera y el contenido, explica cada campo, convierte las fechas exp, iat y nbf a una fecha legible y te dice si el token está vigente o caducado. Con la clave secreta o la clave pública, además, verifica la firma. Todo ocurre en tu navegador: ni el token ni la clave se envían a ningún sitio.

Un JWT no está cifrado. Cualquiera que tenga el token puede leer su contenido, como hace esta página; la firma solo garantiza que nadie lo ha modificado. Por eso un JWT nunca debe llevar contraseñas ni datos que no puedan leer otros. Y aunque esta página no envía nada, trata los tokens de producción como contraseñas: no los pegues en herramientas que no sepas que funcionan solo en tu navegador.

Cómo es un JWT

Un JWT son tres partes en base64url (base64 con - y _ en lugar de + y /, y sin el relleno =) separadas por puntos. Con el token de ejemplo de esta página:

ParteEn el ejemploQué contiene
CabeceraeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Un JSON con el algoritmo de la firma y el tipo de token: {"alg":"HS256","typ":"JWT"}
ContenidoeyJpc3MiOiJodHRwczovL2F1dGguZWplbXBsby5j…Un JSON con los datos (en inglés, claims): quién es el usuario, para qué sirve el token y hasta cuándo vale
Firmavvs2LG2D-omlQHJlGND5G_husN0OlbIb4OO1fF78dMkLa firma de las dos partes anteriores, hecha con la clave del emisor. Si cambia un solo carácter del token, deja de cuadrar

Los campos registrados

La norma de JWT (RFC 7519) define siete campos de uso común. Ninguno es obligatorio, pero si están, tienen este significado:

CampoNombreQué es
issEmisor (issuer)Quién creó y firmó el token, normalmente la URL del servidor de autenticación
subSujeto (subject)A quién se refiere: casi siempre el identificador del usuario
audAudiencia (audience)Para qué servicio es el token. Un servicio debe rechazar los tokens que no son para él
expCaducidad (expiration)A partir de cuándo deja de valer
nbfNo antes de (not before)Desde cuándo vale
iatEmitido (issued at)Cuándo se creó
jtiIdentificador (JWT ID)Un identificador único, para poder revocar el token o detectar que se reutiliza

Las tres fechas van como fecha Unix en segundos, no en milisegundos: un error frecuente al generar tokens desde JavaScript, donde Date.now() devuelve milisegundos. La herramienta avisa si una fecha parece estar en milisegundos.

Cómo se verifica la firma

Depende del algoritmo que diga la cabecera (alg):

  • HS256, HS384 y HS512 (HMAC). El mismo secreto firma y verifica, así que lo conocen tanto el emisor como quien comprueba el token. Para verificar, escribe el secreto; si lo tienes en base64, marca la casilla.
  • RS, PS, ES y EdDSA (clave pública). El emisor firma con su clave privada y cualquiera verifica con la pública. Pega la clave pública en PEM (-----BEGIN PUBLIC KEY-----) o en JWK. También sirve el conjunto de claves (JWKS) completo que publican los proveedores, a menudo en /.well-known/jwks.json: la herramienta elige la clave por el kid del token.

Dos errores de seguridad clásicos tienen que ver con el algoritmo. Hay servidores que aceptan tokens con "alg": "none", sin firma, y otros que dejan que el token elija el algoritmo, de modo que un atacante usa la clave pública RSA como si fuera un secreto HMAC. La defensa es la misma en los dos casos: el servidor debe fijar qué algoritmos acepta, como hace el parámetro algorithms de PyJWT en el ejemplo de abajo.

Leer un JWT en Python sin bibliotecas

Leer el contenido no requiere ninguna clave: basta decodificar el base64url. Solo hay que añadir el relleno que JWT quita:

import base64
import json

TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZWplbXBsby5jb20iLCJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiYXBpLmVqZW1wbG8uY29tIiwibmFtZSI6IkFuYSBHYXJjXHUwMGVkYSIsInJvbGVzIjpbImxlY3RvciIsImVkaXRvciJdLCJpYXQiOjE3NjAwMDAwMDAsImV4cCI6MTc2MDAwMzYwMH0.vvs2LG2D-omlQHJlGND5G_husN0OlbIb4OO1fF78dMk"


def parte(segmento: str) -> dict:
    relleno = "=" * (-len(segmento) % 4)  # base64url va sin relleno
    return json.loads(base64.urlsafe_b64decode(segmento + relleno))


cabecera, contenido, firma = TOKEN.split(".")
print(parte(cabecera))
print(parte(contenido))
$ python3 leer.py{'alg': 'HS256', 'typ': 'JWT'}
{'iss': 'https://auth.ejemplo.com', 'sub': '1234567890', 'aud': 'api.ejemplo.com', 'name': 'Ana García', 'roles': ['lector', 'editor'], 'iat': 1760000000, 'exp': 1760003600}

Verificar un JWT en Python con PyJWT

Para fiarse del contenido hay que verificar la firma, la caducidad y la audiencia, y para eso conviene usar una biblioteca. Con PyJWT:

import jwt  # pip install "pyjwt[crypto]"

TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZWplbXBsby5jb20iLCJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiYXBpLmVqZW1wbG8uY29tIiwibmFtZSI6IkFuYSBHYXJjXHUwMGVkYSIsInJvbGVzIjpbImxlY3RvciIsImVkaXRvciJdLCJpYXQiOjE3NjAwMDAwMDAsImV4cCI6MTc2MDAwMzYwMH0.vvs2LG2D-omlQHJlGND5G_husN0OlbIb4OO1fF78dMk"
SECRETO = "clave-secreta-de-ejemplo-de-decodigo-2026"

try:
    datos = jwt.decode(TOKEN, SECRETO, algorithms=["HS256"], audience="api.ejemplo.com")
except jwt.ExpiredSignatureError:
    print("La firma es buena, pero el token ha caducado")
except jwt.InvalidTokenError as e:
    print("Token no válido:", e)

# Para examinar un token caducado, sin dar por buena la caducidad:
datos = jwt.decode(TOKEN, SECRETO, algorithms=["HS256"], audience="api.ejemplo.com",
                   options={"verify_exp": False})
print(datos["name"], datos["roles"])
$ python3 verificar.pyLa firma es buena, pero el token ha caducado
Ana García ['lector', 'editor']

El token de ejemplo caducó en octubre de 2025, así que jwt.decode lanza ExpiredSignatureError aunque la firma sea buena. Fíjate en que algorithms es una lista fija y en que audience obliga a que el token sea para este servicio.

Más herramientas para desarrolladores: el formateador de JSON, el decodificador de JWT y el conversor de fecha Unix. Todas, en decodigo.com/herramientas.

Normas: RFC 7519 (JWT), RFC 7515 (firmas JWS) y RFC 7518 (algoritmos). La verificación usa WebCrypto, la criptografía del propio navegador. Se comprobó con 26 tokens firmados con PyJWT 2.10.1 en los 13 algoritmos (HS256 a HS512, RS256 a RS512, PS256 a PS512, ES256 a ES512 y EdDSA), con claves en texto, base64, PEM y JWK: todos verifican con su clave, y ninguno con otra clave, con el contenido cambiado o con un bit de la firma cambiado. EdDSA necesita un navegador reciente.