Qué es realmente un JWT
Un JSON Web Token es una cadena de texto. Ahí está todo el truco. En lugar de entregar al cliente un identificador de sesión y guardar la sesión en el servidor, le entregas una afirmación firmada — «este es el usuario 4821, estos son sus permisos, esto caduca a las 14:05» — y dejas que la lleve consigo. Cualquier servicio que confíe en la clave de firma puede leer la afirmación y actuar sin consultar una base de datos.
Eso hace que los JWT sean muy cómodos y algo peligrosos, por el mismo motivo: los datos están en manos del cliente. Un token que emites puede leerlo quien lo tenga, no se puede retirar antes de que caduque y será aceptado por cualquier cosa que confíe en la firma. Casi todos los problemas con JWT en producción nacen de olvidar uno de esos tres hechos.
Un token parece tres bloques de ruido separados por puntos:
eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleS0yIn0.eyJzdWIiOiI0ODIxIiwiZXhwIjoxNzY1MTExMTExfQ.3xS7_c9Nn-aQ
No está cifrado. Los dos primeros bloques son JSON en Base64url, un alfabeto apto para URL que
cambia + y / por - y _ y elimina el relleno
final de =. Cualquiera puede convertirlos otra vez en JSON en un segundo, que es
exactamente lo que hace un decodificador
JWT.
Las tres partes y la función de cada una
Divide el token por los puntos y tendrás la cabecera, la carga útil y la firma.
La cabecera
Pequeña y casi siempre sobre claves. alg nombra el algoritmo de firma,
typ suele ser JWT, y kid es un identificador de clave que
dice al verificador qué clave pública debe obtener cuando el emisor tiene más de una. Si un token
deja de validarse de repente tras una rotación de claves, kid es lo primero que hay
que mirar.
La carga útil
Los claims: la afirmación que de verdad se hace. Algunos están registrados en la especificación
(iss, sub, aud, exp, nbf,
iat, jti) y el resto es lo que tu emisor haya decidido añadir:
scope, roles, tenant_id, una dirección de correo, un
indicador de función.
La firma
Un MAC o una firma digital sobre las dos primeras partes, tal y como aparecen en la cadena. Ese detalle importa: la firma cubre los bytes codificados, así que volver a serializar el JSON y recodificarlo produce un token que ya no se valida aunque los datos sean idénticos. Nunca reconstruyas un token para «dejarlo más limpio».
Cómo decodificar un token y qué mirar primero
Pega la cadena completa en un decodificador, puntos incluidos. Si la copiaste de una petición,
quita el prefijo Bearer , o usa una herramienta que lo quite por ti. Después lee en
este orden:
- ¿Se ha decodificado? Si el decodificador dice que las partes no son JSON Base64url válido, el token llegó truncado o alterado. Una cookie que ha tocado un límite de tamaño y una cabecera que se ha partido en líneas se ven así.
exp. La caducidad explica la mayoría de los «funcionaba hace cinco minutos», y es lo primero que conviene descartar.audeiss. Un token emitido para otra audiencia es un token perfectamente válido que tu API hace bien en rechazar.- Los claims personalizados. Solo cuando los estándar cuadran merece la pena
discutir si
scopecontiene lo que esperabas.
Decodifica en local siempre que puedas. Un token es una credencial viva, así que pegarlo en un sitio cualquiera es el mismo tipo de error que pegar una contraseña: cualquier herramienta que merezca la pena decodifica en el navegador y no envía nada a ninguna parte.
Los claims que deciden si un token funciona
Cinco claims hacen casi todo el trabajo, y cada uno puede rechazar una petición por sí solo.
exp: caducidad, como marca de tiempo Unix en segundos. Un valor en milisegundos es un error clásico: cae en el año 57000 y el token nunca caduca. Un buen decodificador muestra la fecha real y cuánto tiempo hace que pasó.nbf: not before. El token es inválido hasta ese momento. Se usa poco a propósito y a veces se establece por accidente en una máquina con el reloj adelantado.iat: emitido en. Sirve para rechazar tokens que técnicamente no han caducado pero son sospechosamente antiguos, y para detectar un problema de reloj cuandoiatestá en el futuro.aud: audiencia, el destinatario previsto. Una API que se salta esta comprobación aceptará encantada un token que otro de tus servicios emitió para sí mismo, y eso es una vía real de escalada de privilegios entre microservicios.iss: emisor. Fíjalo al emisor exacto que esperas y obtén las claves solo del conjunto de claves publicado por ese emisor.
jti es el raro del grupo: un identificador único de token. Solo significa algo si
lo guardas y lo compruebas, pero es lo que hace posible revocar un único token.
Decodificar no es verificar
Este es el punto que pilla a la gente. Decodificar lee el Base64url y te muestra el JSON. Te dice lo que un token afirma. No te dice nada sobre si esa afirmación es cierta.
Cualquiera puede escribir {"sub":"1","role":"admin"}, pasarlo a Base64url, pegar
cualquier cadena al final como firma y entregártelo. Se decodificará perfectamente en cualquier
decodificador, también en el nuestro. Lo que impide que funcione es la verificación: recalcular la
firma con la clave del emisor y rechazar el token si no coincide. Ese paso necesita un secreto o
una clave pública, corresponde a tu servidor y ninguna herramienta de navegador puede hacerlo por
ti.
Así que trata un decodificador como una lectura, nunca como una prueba. «Lo decodifiqué y dice admin» no es un hallazgo de seguridad. «Mi servidor lo verificó y dice admin» sí lo es.
Por qué un token con buen aspecto recibe un 401
Cuando el token se decodifica bien y la API sigue rechazándolo, casi siempre es una de estas:
- Caducado. Comprueba
expcontra el reloj del servidor, no contra el tuyo. - Desajuste de reloj. Un servidor un par de minutos adelantado rechazará tokens recién emitidos. La mayoría de las bibliotecas permiten un pequeño margen precisamente por eso; si la tuya está a cero, conviene revisarlo.
- Audiencia equivocada. Habitual cuando un proveedor de identidad sirve a varias API.
- Rotación de claves. El emisor firmó con una clave nueva, el verificador sigue
con el conjunto antiguo en caché y
kidya no resuelve. - El token nunca llegó. Una cabecera
Authorizationausente o mal formada, una cookie perdida o un proxy que elimina cabeceras. Comprueba qué recibió realmente el servidor antes de culpar al token: la búsqueda de códigos de estado HTTP y nuestra guía de códigos de estado ayudan a distinguir un 401 de un 403 que significa algo muy distinto.
¿401 o 403?
Un 401 significa que la credencial falta, ha caducado o no se puede verificar:
arregla el token. Un 403 significa que la credencial estaba bien y la respuesta sigue
siendo no: arregla los permisos, o el claim scope. Los servicios que devuelven 401
ante un fallo de autorización mandan a la gente a depurar un token que nunca era el problema.
Elegir y comprobar el algoritmo de firma
En la práctica importan dos familias. HS256 es un HMAC con un secreto compartido: quien puede verificar también puede firmar, lo cual está bien dentro de un servicio y encaja mal entre organizaciones. RS256 y ES256 usan una clave privada para firmar y una pública para verificar, así que una API puede validar tokens que nunca habría podido emitir.
Luego está alg: none, un token sin firmar que la especificación permite y que nunca
deberías aceptar. Existe en el estándar, lo que significa que ha existido en bibliotecas, lo que
significa que se ha explotado: a un verificador que toma el algoritmo de la propia cabecera del
token se le puede decir «este no está firmado, confía sin más». Un truco relacionado
entrega a un verificador RS256 un token HS256 firmado usando la clave pública como secreto HMAC.
La defensa es una línea de política: decide qué algoritmos aceptas antes de mirar el
token y rechaza cualquier otro. La cabecera es una pista sobre qué clave usar, no una
instrucción sobre si comprobar la firma. Si tu decodificador marca alg: none en rojo,
es por esto.
Qué no debe ir nunca en una carga útil
Como la carga útil está codificada y no cifrada, cada claim es público para quien tenga el token, y los tokens acaban en el almacenamiento del navegador, en registros, en sistemas de errores, en tickets de soporte y en capturas de pantalla. Mantén fuera de la carga útil:
- Cualquier cosa que no pondrías en una URL: contraseñas, claves de API, números de tarjeta, datos de salud.
- Identificadores internos que preferirías no publicar: claves primarias de base de datos, nombres de host internos, condiciones de licencia.
- Bloques grandes. Cada petición lleva el token; un kilobyte de claims es un kilobyte en cada llamada, y los tokens enormes son una forma fiable de topar con los límites de tamaño de cabecera de un proxy.
Mantén también los tokens fuera de las URL. Un token en una cadena de consulta acaba en los
registros del servidor, en cabeceras Referer y en el historial del navegador. Envíalo
en la cabecera Authorization y asegúrate de que tu sitio define un
Referrer-Policy sensato: nuestra guía de cabeceras de seguridad lo cubre, y el
verificador de cabeceras de seguridad te
dirá qué estás enviando hoy.
Caducidad, refresco y el problema de la revocación
Un JWT no se puede llamar de vuelta. Una vez emitido, es válido hasta exp, pase lo
que pase con la cuenta que hay detrás. Desactiva a un usuario y su token de acceso seguirá
funcionando hasta que se agote. Ese es el precio de no consultar una base de datos en cada
petición.
La respuesta habitual son dos tokens. Un token de acceso de vida corta —minutos, no días— lleva los claims y hace el trabajo. Un token de refresco de vida larga se guarda en el servidor, se comprueba contra una base de datos cada vez que se usa y puede revocarse al instante. Cerrar la sesión de alguien significa entonces invalidar el token de refresco y esperar la ventana corta hasta que el de acceso muera solo.
Elige la ventana de caducidad con intención: es el hueco entre revocar el acceso y que el acceso
se detenga de verdad. Quince minutos es un compromiso habitual. Las horas son difíciles de
justificar; los días significan que no tienes revocación alguna. Y si necesitas revocación
inmediata, mantén una lista de denegación de valores jti, momento en el que vuelves a
consultar un almacén en cada petición y una sesión normal puede ser el diseño más simple.
Elijas lo que elijas, el bucle de depuración es el mismo: decodifica el token, lee
exp y aud, confirma el reloj y luego verifica la firma en el servidor.
Cuatro pasos, en ese orden, y casi todos los problemas de tokens caen en los dos primeros.
Herramientas y guías relacionadas:
- Decodificador JWT: Decodifica un JSON Web Token y lee su cabecera, carga útil, claims y caducidad.
- Codificador y decodificador Base64: Codifica texto a Base64 o decodifícalo, con salida URL-safe y UTF-8 bien tratado.
- Base64 explicado: cómo funcionan la codificación y la decodificación: Cómo Base64 convierte bytes en texto
Preguntas frecuentes
¿Puedo decodificar un JWT sin la clave secreta?⌄
Sí. La cabecera y la carga útil están codificadas en Base64url, no cifradas, así que cualquiera que tenga el token puede leerlas. La clave solo se necesita para verificar la firma. Por eso mismo nunca debes poner datos sensibles en una carga útil.
¿Es seguro pegar un JWT en un decodificador en línea?⌄
Solo si la decodificación ocurre en tu navegador. Un token es una credencial viva hasta que caduca, así que una herramienta que lo suba a un servidor está manejando tu credencial. Nuestro decodificador funciona íntegramente en la página y no envía el token a ninguna parte, pero la costumbre más segura es usar tokens caducados o de prueba cuando puedas.
¿Por qué mi token se decodifica bien y aun así devuelve 401?⌄
Decodificar solo demuestra que la cadena está bien formada. Los culpables habituales son un exp que ya pasó, una diferencia de reloj entre el emisor y el verificador, un aud que nombra otra API, o un kid que el verificador no puede resolver tras una rotación de claves.
¿Qué diferencia hay entre un JWT y una cookie de sesión?⌄
Una cookie de sesión es un puntero: el servidor guarda el estado y lo consulta en cada petición, así que puede revocarse al instante. Un JWT lleva el estado consigo, así que no hace falta consulta alguna, y nada puede retirarse antes de que caduque. Los JWT escalan mejor entre servicios; las sesiones te dan control.
¿Debo usar HS256 o RS256?⌄
HS256 comparte un único secreto, así que quien puede verificar también puede firmar. Eso es aceptable dentro de un solo servicio. RS256 y ES256 firman con una clave privada y verifican con una pública, que es lo que quieres en cuanto hay más de una parte implicada. En cualquier caso, fija en tu verificador los algoritmos aceptados y nunca confíes en el valor alg del token.
Más de 15 años de experiencia práctica en desarrollo web y seguridad. Centrado en la seguridad web práctica: mantener los sitios seguros, fiables y fáciles de diagnosticar. BuildToolForge existe para que estas comprobaciones sean gratuitas, privadas y comprensibles. Las herramientas se ejecutan en tu navegador siempre que es posible, y nada de lo que introduces en una herramienta se guarda.
Cómo escribimos nuestras guías: Cada guía nace de la experiencia práctica, se contrasta con los estándares oficiales y se prueba con nuestras propias herramientas en dominios reales. Actualizamos las guías cuando cambian los estándares o las herramientas. ¿Has visto un error? Escríbenos a contact@buildtoolforge.com.