Skip to content
Volver al blog
Seguridad

Cómo decodificar un token JWT: guía práctica para desarrolladores

Decodifica JWT sin secreto ni red: anatomía, base64url y código en Node.js, Python y Go. Inspecciona header, payload y claims con un decodificador que corre en tu navegador.

12 min de lectura

Cómo decodificar un token JWT: guía práctica para desarrolladores

Tu API acaba de devolver 401 Unauthorized. El header Authorization: Bearer eyJhbGciOi... se ve bien a simple vista. ¿El token expiró, el audience estaba mal, o el emisor rotó una clave? No puedes responder esa pregunta sin leer lo que hay realmente dentro del token, y para decodificarlo no te hace falta un secreto, ni una librería, ni siquiera conexión a internet. Un JWT son tres bloques codificados en base64url unidos por puntos. Decodificarlo es mecánico: dividir, base64url, JSON.parse. Sin magia y sin criptografía.

Esta guía recorre la anatomía, te muestra cómo decodificar un JWT en Node.js, Python, Go y el navegador, cubre la diferencia entre decodificar y verificar que confunde a la mayoría de los equipos, y enumera los fallos reales con los que te vas a topar. Si solo necesitas inspeccionar un token ahora mismo, salta a nuestro decodificador JWT gratuito: corre entero en tu navegador, así los tokens de producción nunca salen de tu dispositivo.

¿Qué es un JWT? Anatomía rápida

Un JSON Web Token (JWT) es una credencial compacta y segura para URLs definida en el RFC 7519. Transporta claims (datos sobre el usuario y sobre el propio token) entre dos partes. Un JWT son tres piezas codificadas en base64url unidas por puntos: un header, un payload y una firma.

Aquí tienes un token real separado para que veas la estructura:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9       ← header
.
eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0   ← payload
.
4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0       ← signature

El header describe cómo se firma el token, normalmente { "alg": "HS256", "typ": "JWT" }. El payload transporta los claims: los registrados como sub, exp, iat y los personalizados como role o tenant. La firma es una prueba criptográfica calculada sobre el header y el payload que permite al receptor detectar manipulaciones. Base64url es una variante de base64 segura para URLs; revisa nuestra guía de Base64 para principiantes si quieres una introducción de diez minutos.

Los JWT aparecen en casi todos los rincones de la autenticación moderna: tokens de acceso OAuth 2.0, ID tokens de OpenID Connect, credenciales de API emitidas por Auth0, Okta, Clerk, Supabase, Firebase, y tokens que viajan entre microservicios dentro de una malla. Son el formato de credencial por defecto de la última década.

Antes de seguir, una línea que conviene interiorizar: los JWT están codificados, no cifrados. Cualquiera que tenga el token puede leer todos los claims. La firma prueba el origen; no oculta el contenido. Ese hecho tan simple dicta todo lo demás en esta guía: qué es seguro poner en el payload, por qué decodificar no necesita un secreto, y por qué la verificación de firma no es negociable en el servidor.

Cómo funciona la decodificación de JWT (base64url, no descifrado)

Decodificar un JWT no es una operación criptográfica. Son cuatro pasos mecánicos:

  1. Divide el token por . en exactamente tres segmentos.
  2. Decodifica el primer segmento con base64url y parséalo como JSON: ese es el header.
  3. Decodifica el segundo segmento con base64url y parséalo como JSON: ese es el payload.
  4. Deja el tercer segmento (la firma) como bytes crudos; verificarla requiere la clave.

Ese es todo el algoritmo. No hace falta ninguna librería. Cualquier lenguaje con base64 y un parser JSON puede decodificar un JWT en cinco líneas. Nuestro codificador/decodificador Base64 hace los pasos 2 y 3 a mano si quieres ver la mecánica.

¿Qué es base64url?

Base64url es base64 normal con tres ajustes para que la salida sea segura en URLs y headers HTTP: - reemplaza a +, _ reemplaza a /, y se elimina el relleno = final. Si alimentas base64url crudo en un decodificador base64 estándar sin revertir esas sustituciones, obtienes basura o un error. La guía avanzada de Base64 cubre los casos límite del padding con más detalle.

base64 estándarbase64url
AlfabetoA-Z a-z 0-9 + /A-Z a-z 0-9 - _
Padding= requerido al finalEliminado
¿Seguro en URLs?No
EjemploPDw/Pz8+PDw_Pz8-

Una aclaración adicional: no puedes descifrar la firma en el cliente. Decodificar es una operación unidireccional de bytes codificados a JSON. Verificar la firma es una operación separada que necesita o bien el secreto HMAC (para la familia HS) o la clave pública del emisor (para RS, PS, ES, EdDSA).

Por qué no necesitas el secreto para decodificar

Porque el payload es base64url más JSON, no texto cifrado. El secreto solo entra en juego cuando quieres probar que el token no ha sido manipulado: la verificación de firma. Cualquiera que esté en la ruta de red, cualquiera que tenga el token en una línea de log, cualquiera con un navegador puede leer todos los claims que pongas dentro. Por eso nunca debes poner contraseñas, claves de API ni PII más allá de lo que el receptor ya conoce dentro del payload de un JWT. Para el modelo de amenazas más amplio, lee nuestra guía de mejores prácticas de seguridad.

Decodifica JWT online en 3 clics con un decodificador gratuito

A veces solo necesitas una respuesta ya: ¿este token expiró?, ¿el claim aud es el que yo creo?, ¿el header dice alg:none? El camino más rápido es nuestro decodificador JWT online. Está pensado para la ruta de respuesta a incidentes a las 2 de la mañana.

  1. Pega el token completo en el área de entrada. Incluye los tres segmentos separados por puntos.
  2. Lee el header y el payload decodificados junto con los indicadores de estado en la parte superior: algoritmo, fecha de emisión, expiración y una etiqueta roja Expired si exp ya está en el pasado.
  3. Copia el panel que necesites a tu reporte de bug, hilo de Slack o fixture de prueba.

Por qué es seguro para tokens de producción reales:

  • Corre al 100% en el navegador. La decodificación usa atob nativo y JSON.parse. No hay ni una sola petición de red.
  • Sin logs, sin tracking, sin cookies, sin registro.
  • Funciona sin conexión una vez cargada la página.

La herramienta JWT Decoder es agnóstica al algoritmo. Como decodificar solo necesita base64url y JSON, lee todas las variantes de JWS: HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA y alg:none. Solo la verificación de firma depende del algoritmo, y la verificación no es algo que quieras que haga una herramienta web pública; volveremos a esto en un momento.

¿Necesitas decodificar en base64 un segmento a mano para contrastar la herramienta? Usa nuestro codificador/decodificador Base64 y alimenta cada segmento como base64url.

Cómo decodificar JWT en código (Node.js, Python, Go, Navegador)

Para todo lo que no sea depuración interactiva (middleware, tests, scripts de migración, herramientas CLI) vas a recurrir a una librería. Aquí tienes el código mínimo para decodificar un JWT en los cuatro entornos con los que probablemente te toparás, con la ruta de solo lectura y la ruta de verificación una al lado de la otra. Cada fragmento es copiable y produce las salidas que aparecen en los comentarios.

Decodificar JWT en Node.js (jsonwebtoken)

// npm install jsonwebtoken
const jwt = require('jsonwebtoken');

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' +
              '.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0' +
              '.4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0';

// Solo decodifica — NO verifica la firma
const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header);   // { alg: 'HS256', typ: 'JWT' }
console.log(decoded.payload);  // { sub: 'user_123', exp: 1999999999 }

// Verificar — la ruta de producción
const secret = process.env.JWT_SECRET;
const verified = jwt.verify(token, secret, { algorithms: ['HS256'] });

Pasa siempre una lista explícita de algorithms permitidos a verify. Omitirla ha permitido históricamente a los atacantes degradar un token RS256 a HS256 firmando con la clave pública como secreto HMAC: el clásico ataque de confusión de algoritmos. La allowlist es tu defensa.

Decodificar JWT en Python (PyJWT)

# pip install PyJWT
import jwt

token = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
    ".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0"
    ".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"
)

# Solo decodifica — inseguro para auth, está bien para inspección
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded)  # {'sub': 'user_123', 'exp': 1999999999}

# Header sin tocar el payload
header = jwt.get_unverified_header(token)
print(header)   # {'alg': 'HS256', 'typ': 'JWT'}

# Verificar — ruta de producción
payload = jwt.decode(
    token,
    key="your-hs256-secret",
    algorithms=["HS256"],
    audience="api.example.com",
)

PyJWT se niega a verificar sin una lista de algorithms: un default sensato que previene el mismo ataque de confusión que advierte el ejemplo de Node.

Decodificar JWT en Go (golang-jwt/jwt/v5)

// go get github.com/golang-jwt/jwt/v5
package main

import (
    "fmt"
    "github.com/golang-jwt/jwt/v5"
)

func main() {
    tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
        ".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0" +
        ".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"

    // Solo decodifica
    parser := jwt.NewParser()
    claims := jwt.MapClaims{}
    _, _, err := parser.ParseUnverified(tokenString, claims)
    if err != nil {
        panic(err)
    }
    fmt.Println(claims["sub"], claims["exp"]) // user_123 1.999999999e+09

    // Verificar
    secret := []byte("your-hs256-secret")
    token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
        if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"])
        }
        return secret, nil
    })
    fmt.Println(token.Valid, err)
}

En el closure keyFunc es donde fuerzas la familia de algoritmos: rechaza cualquier cosa que no sea el método que esperas antes de devolver la clave.

Decodificar JWT en el navegador (cero dependencias)

A veces no quieres ninguna dependencia: un panel de depuración rápido, una extensión de navegador, un pequeño indicador de UI que muestra el rol del usuario actual. Con las APIs nativas del navegador basta:

function decodeJwt(token) {
  const [h, p] = token.split('.');
  const pad = (s) => s + '==='.slice((s.length + 3) % 4);
  const decodeSegment = (s) => {
    const b64 = pad(s).replace(/-/g, '+').replace(/_/g, '/');
    const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
    return JSON.parse(new TextDecoder().decode(bytes));
  };
  return { header: decodeSegment(h), payload: decodeSegment(p) };
}

const { header, payload } = decodeJwt(token);
console.log(header);   // { alg: 'HS256', typ: 'JWT' }
console.log(payload);  // { sub: 'user_123', exp: 1999999999 }

El paso por TextDecoder importa en cualquier token con payload no ASCII (emojis en un nombre para mostrar, cirílico en un preferred_username): atob a secas devuelve una cadena binaria, lo que rompe JSON.parse sobre UTF-8 multibyte. Esto es exactamente lo que corre nuestro decodificador JWT online localmente en tu navegador, sin la UI.

Tabla comparativa

LenguajeSolo decodificarVerificarLibrería
Node.jsjwt.decode(token)jwt.verify(token, key, { algorithms: [...] })jsonwebtoken
Pythonjwt.decode(token, options={"verify_signature": False})jwt.decode(token, key, algorithms=[...])PyJWT
Goparser.ParseUnverified(token, claims)jwt.Parse(token, keyFunc)golang-jwt/jwt/v5
Navegadoratob + TextDecoder + JSON.parsePregunta a tu backend

Decodificar vs verificar: la diferencia crítica

Decodificar un JWT lee sus claims; verificar un JWT prueba que esos claims no fueron manipulados. Decodificar no confía nunca; verificar es la confianza. Esa única distinción separa una implementación de auth que funciona de un CVE.

DecodificarVerificar
¿Necesita secreto/clave?No
¿Corre en el cliente?Con seguridadNunca
¿Prueba autenticidad?No
¿Verifica expiración?Opcional
Caso de usoDepuración, inspecciónAutenticación, autorización

Nunca tomes una decisión de autorización a partir de un JWT decodificado (sin verificar). No en un middleware, no en un hook de React, no en una función serverless que tú crees que está detrás del gateway. Los claims decodificados dicen lo que el token afirma; los claims verificados dicen lo que el emisor firmó. Un atacante que le pase a tu servidor un token fabricado a mano sin una firma válida puede poner lo que quiera en el payload; solo la verificación de firma lo rechaza.

Un detalle más sobre la familia HMAC: si usas HS256, la entropía de tu secreto es todo el juego. Un secreto corto y adivinable se rompe por fuerza bruta offline contra cualquier token que un atacante capture, y luego te acuña sus propios tokens y entra por la puerta principal. Usa al menos 256 bits de aleatoriedad real. Revisa nuestra guía de fortaleza de secretos HMAC para la aritmética de por qué esto importa.

Referencia de claims JWT comunes

Cada JWT que encuentres usa algún subconjunto de los claims registrados del RFC 7519. Memoriza la lista corta:

ClaimNombreEjemploNotas
issIssuer (emisor)https://auth.example.comQuién acuñó el token
subSubject (sujeto)user_123Normalmente el ID del usuario
audAudience (audiencia)api.example.comPara quién es el token; debe coincidir en el servidor
expExpiration1715003600Segundos Unix; pasado = expirado
iatIssued At1715000000Segundos Unix en que se acuñó el token
nbfNot Before1715000060Momento más temprano en que el token es usable
jtiJWT IDd1f8…Único por token; previene replay
kidKey ID (header)key-2025-01Qué clave de tu JWKS firmó este token

Los claims específicos de la aplicación viven junto a estos: role, scope, email, tenant_id, lo que sea que emita tu proveedor de identidad. Mantenlos cortos: cada byte viaja en cada petición.

Para leer fechas humanas desde iat y exp, prueba nuestro convertidor de timestamps Unix. Pega el número, obtén la fecha en tu zona horaria local, detecta un bug de desfase en un segundo.

Solución de problemas: ¿por qué no se decodifica mi JWT?

Cinco fallos reales, en orden aproximado de frecuencia. Cada uno está planteado como Síntoma → Causa → Solución.

  1. “Formato JWT inválido: se esperaban tres segmentos.” Copiaste solo el payload, o la shell envolvió el token en varias líneas y solo capturaste la primera. Solución: vuelve a copiar el valor completo xxx.yyy.zzz del cuerpo de respuesta original, no de una terminal renderizada. Los valores largos de una sola línea sobreviven mejor en la pestaña Network de las devtools del navegador que en una terminal con scroll.
  2. Cinco segmentos en vez de tres. Tienes un JWE (JWT cifrado), no un JWS. El formato es header.encryptedKey.iv.ciphertext.tag. Un decodificador leerá el header pero el payload es texto cifrado. Solución: decodificar el payload requiere la clave de descifrado, que normalmente maneja tu SDK de auth del lado servidor, no una herramienta de depuración.
  3. Error de base64url en un token que por lo demás parece válido. El token se URL-encodeó en algún lugar de la ruta de copia (una cookie, una URL de redirección, un log de proxy capturado). Verás %2E o %2B literales en la cadena. Solución: decodifica la URL primero y luego pasa el resultado al decodificador JWT.
  4. Error de parseo JSON en el payload. Una terminal o cliente de chat insertó saltos de línea por soft-wrap, o un script pegó comillas tipográficas alrededor de un identificador. Solución: mira los bytes crudos de la respuesta (curl con -o file.txt, o la vista Raw de devtools), quita los espacios en blanco y pega de nuevo.
  5. Decodifica limpio pero el backend igual lo rechaza. Eso no es un problema de decodificación; es un problema de verificación. El token es estructuralmente válido; algo que el servidor revisa (firma, aud, exp, desfase de reloj, lookup de kid) está fallando. Salta a la siguiente sección.

Dos menciones honorables que no son errores de parseo pero vale la pena detectar mientras tienes el decodificador abierto: un valor de alg igual a none en el header (trátalo como hostil en producción) y un valor de exp en el pasado (el decodificador igual muestra los claims para que puedas depurar; ese es el comportamiento correcto, y nuestra herramienta lo marca con una etiqueta roja Expired).

Cuando decodificar no es suficiente: verificación de firma

La decodificación termina en “esto es lo que afirma el token”. La verificación es lo que convierte una afirmación en una decisión de confianza. La firma es una prueba, calculada con la clave privada del emisor o un secreto compartido, que ata el header y el payload: cambia un solo byte y la verificación de firma falla. Sin esa verificación, cualquiera que pueda hacer POST a tu endpoint puede fabricarte un token de “admin” a mano editando el payload y saltándose la firma por completo.

Nunca aceptes alg:none.

La verificación de producción, en todo lenguaje y framework, se parece a esta lista de verificación. Trata los ítems que falten como bugs:

  • Pasa una allowlist explícita de algorithms: ['RS256'] (o lo que uses). Así derrotas los ataques de confusión de algoritmos.
  • Comprueba que aud coincida con el identificador de tu servicio y que iss coincida con la URL de emisor esperada.
  • Valida exp contra la hora actual con a lo sumo 60 segundos de tolerancia al desfase de reloj.
  • Si rotas claves, busca la clave pública por kid desde un endpoint JWKS; nunca codifiques en duro una sola clave para siempre.
  • Revoca de forma efectiva manteniendo exp corto (minutos, no días) y opcionalmente manteniendo una denylist de jti para tokens de alto valor.

Toda librería JWT mainstream expone estas opciones en una sola llamada. Si tu código de verificación no las configura, estás dejando los defaults, y los defaults históricamente han sido el bug. Para el modelo completo de amenazas, revisa nuestra guía de mejores prácticas de seguridad.

Preguntas frecuentes

¿Puedo decodificar un JWT sin la clave secreta?

Sí. El header y el payload están codificados en base64url, no cifrados; cualquiera que tenga el token puede leer sus claims. El secreto o la clave pública solo se necesita para verificar la firma. Esto es por diseño: el payload está pensado para ser legible, para que el receptor pueda tomar decisiones de autorización.

¿Es seguro pegar mi JWT de producción en un decodificador online?

Solo si el decodificador corre en tu navegador y nunca sube el token. Nuestro decodificador JWT parsea localmente con atob nativo y JSON.parse: nada se envía a ningún servidor. Los depuradores remotos que hacen POST de tu token a una API deben tratarse como filtraciones de credenciales.

¿Cuál es la diferencia entre decodificar y verificar un JWT?

Decodificar solo lee los claims: no necesita clave y no prueba nada. Verificar comprueba la firma contra la clave del emisor y confirma que el token no ha sido manipulado. Nunca tomes una decisión de autenticación a partir de un token decodificado pero no verificado.

Mi JWT se ve truncado — ¿qué cuenta como formato válido?

Un JWT válido tiene exactamente tres segmentos base64url separados por puntos: header.payload.signature. Cinco segmentos significa que tienes un JWT cifrado (JWE), no un JWS. Cero puntos significa que copiaste solo un segmento de una línea de terminal envuelta.

¿Por qué el decodificador sigue mostrando un token expirado?

Un decodificador lee los claims independientemente de la validez, para que puedas depurar rechazos. Solo un verificador rechaza tokens expirados. Nuestra herramienta muestra una etiqueta Expired comparando exp contra tu reloj local, así detectas el problema al instante sin entrecerrar los ojos mirando timestamps Unix.

¿Qué algoritmos puedo decodificar?

Todos. Decodificar solo necesita parseo de base64url y JSON: es agnóstico al algoritmo. Eso incluye HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA y alg:none. Solo la verificación depende del algoritmo que hayas elegido.

¿Debería usar jwt-decode o jsonwebtoken en Node.js?

Usa jwt-decode en el frontend cuando solo necesitas leer el payload, por ejemplo para mostrar un nombre de usuario desde un token de acceso. Usa jsonwebtoken en el backend, porque solo el backend puede sostener la clave de firma y ejecutar jwt.verify. No verifiques nunca en el cliente.

Conclusión

Decodificar un JWT no es ni de lejos tan misterioso como sugiere “token criptográfico”. Quédate con cinco conclusiones y no volverás a quedar varado mirando una cadena opaca eyJhbGciOi…:

  • Decodificar es base64url más parseo JSON. No se requiere ningún secreto.
  • Un JWT tiene tres partes: header, payload, firma, unidas por puntos.
  • Decodificar no prueba autenticidad. Verifica siempre en el servidor con la clave del emisor.
  • Rechaza alg:none y pasa siempre una allowlist explícita de algoritmos a verify.
  • No guardes contraseñas, claves privadas ni PII sensible en el payload: es legible por cualquiera que tenga el token.

Marca como favorito nuestro decodificador JWT gratuito para depuración on-call. Pega un token, lee los claims, detecta expiraciones en un segundo, todo sin que tu token salga nunca del navegador.

Artículos relacionados

Ver todos los artículos