Skip to content
This page has been auto-translated and may contain errors.View in English

JSON Web Tokens

Un token opaco necesita una consulta: la cadena no significa nada, así que el servidor tiene que preguntarle a su propio almacenamiento a quién pertenece.

Quita la consulta y algo tiene que reemplazarla. Un JSON Web Token, o JWT, la reemplaza poniendo la respuesta dentro del propio token.

Tres partes, una cadena separada por puntos

Un JWT parece ruido de línea, pero tiene una estructura estricta. Tres partes, unidas por puntos:

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjQ4MjEsInJvbGUiOiJ0ZWFjaGVyIn0.4pcPyMD09olPSyXnrXCjTwXyr4BsezdI1AVTmud2fU4
└────────── header ──────────┘ └────────── payload ─────────┘ └────────── signature ──────────┘
  • Header. Qué algoritmo lo firmó, y que se trata de un JWT.
  • Payload. La información de identidad, llamada claims: quién es el usuario, qué rol tiene, cuándo expira el token, quién lo emitió.
  • Signature. Un sello criptográfico que demuestra que nada fue alterado.

El servidor lo construye tomando el header y el payload, combinándolos con un secreto que solo él conoce, y produciendo la signature. Luego las tres partes se codifican y se unen.

Cualquiera puede leer un JWT. Solo alguien que tenga el secreto puede crear uno que el servidor acepte.

Eso es lo que lo hace confiable sin necesidad de una consulta, y resume toda la idea en una sola frase.

JunoTres partes, una cadena separada por puntos Un sello de cera en un sobre es una buena comparación. Cualquiera puede mirar el sello, y solo la persona que tiene el molde puede crear uno igual.

Lo que significa que el sello no oculta la carta. Demuestra quién la cerró, y que nadie la abrió desde entonces.

JunoTres partes, una cadena separada por puntos Pega un token real en un decodificador y vas a ver el header y el payload en JSON plano de inmediato, sin necesidad de ningún secreto. Hazlo una vez con un token de algo que estés construyendo; eso hace que la siguiente sección tenga mucho más sentido.

La signature es la parte que se te resiste. Solo te dice sí o no, y solo si tienes el secreto.

JunoTres partes, una cadena separada por puntos Que el header sea visible y manipulable por el atacante es el origen de la clásica falsificación de JWT. Pon alg en none, quita la signature, y cualquier servidor que decodifique un token en lugar de verificarlo acepta el payload que tú elegiste.

Esto se ha demostrado contra un middleware que usa jwt.decode, y produce una sesión válida como quien tú digas que eres.

La solución es fijar de antemano qué aceptas en jwt.verify: nombra los algoritmos explícitamente, y fija también issuer y audience. Nunca dejes que el token te diga cómo verificarlo.

Una unidad para recordar mientras estás en eso: clockTolerance está en segundos, no en milisegundos.

El flujo, y qué cambió

  1. El usuario inicia sesión con un nombre de usuario y una contraseña.
  2. El servidor los verifica.
  3. Si todo sale bien, el servidor crea un JWT y lo firma.
  4. El token va al cliente, que lo guarda.
  5. Cada solicitud posterior lo lleva en el header Authorization, exactamente como lo haría un token opaco.
  6. El servidor verifica la signature y lee los claims. Sin consulta, sin almacenamiento.

El paso seis es el único que difiere de los tokens opacos tipo bearer, y cambia el modelo por completo:

Token opaco tipo bearerJWT
ContenidoNada, una cadena aleatoriaClaims codificados: id de usuario, rol, expiración
Para identificar al usuarioConsultarloVerificar la signature, leer los claims
Almacenamiento del servidorUna pequeña tabla de token a usuarioNinguno
Dónde vive la identidadEn el servidorDentro del token

Un token opaco apunta hacia una identidad que vive en el servidor. Un JWT la contiene.

JunoEl flujo, y qué cambió Solo cambió un paso, y es el que decide todo lo demás.

Consultar algo significa que el servidor puede verificar la respuesta más reciente. Leer el token significa que el servidor obtiene la respuesta que era válida cuando el token fue creado.

JunoEl flujo, y qué cambió No tener almacenamiento significa que no hay ningún almacén de sesiones compartido que mantener, ninguna coordinación entre instancias, y escalabilidad horizontal gratis. Eso es una ventaja operativa real, y es la razón por la que los JWT suelen elegirse para microservicios.

El costo aparece el día que necesitas terminar una sesión de inmediato. No hay ningún registro que borrar, así que el token sigue funcionando hasta que expira. Planifica eso antes de necesitarlo, no en medio de un incidente.

JunoEl flujo, y qué cambió Los roles dentro del token son el punto más delicado en la práctica. Degrada a un admin y su token existente seguirá diciendo admin hasta que expire, así que tu cambio de permisos tiene un retraso medido por la vida útil que hayas elegido.

Hay dos salidas, y cada una cuesta algo distinto. Puedes mantener los tokens muy efímeros, de minutos, y refrescarlos seguido, lo que vuelve a introducir una consulta en cada refresco. O puedes mantener una lista blanca o negra de ids de token, lo que vuelve a introducir la consulta que habías eliminado.

Vale la pena ser honesto: ambas opciones son el modelo sin estado recuperando algo de estado. Es un diseño válido, pero no es la ganancia gratuita que promete el discurso.

Cuatro maneras en que sale mal

Un JWT sigue siendo un token tipo bearer, así que cada trampa de los tokens opacos sigue aplicando: si te lo roban, el atacante se convierte en el usuario. Ser autocontenido agrega cuatro trampas más.

Qué sale malPor qué importaLa solución
1Asumir que está encriptadoEstá codificado, no encriptado. Cualquiera que lo tenga puede leer el payload. Divulgación de información en STRIDE, exposición de datos sensibles en OWASPNo pongas en el payload nada que no escribirías en una postal
2Un secreto débil o filtradoSi un atacante adivina o roba el secreto, puede firmar sus propios tokens, incluyendo uno que diga admin. Elevación de privilegiosUn secreto largo y aleatorio, cargado desde el entorno, nunca subido al repositorio
3Un payload sobrecargadoTokens grandes significan headers grandes en cada solicitud: solicitudes lentas, proxies incómodos, logs inflados. Y todo lo que hay ahí se filtra si el token se filtraSolo lo que el servidor necesita en cada solicitud
4Sin expiraciónNo puedes revocar un JWT, así que sin una expiración funciona para siempre. Autenticación rotaSiempre define una, y que sea corta

La trampa 2 es la que convierte un pequeño error en un compromiso total. Un id de sesión filtrado es una sola cuenta; un secreto de firma filtrado es cada cuenta, incluyendo las que todavía no existen.

JunoCuatro maneras en que sale mal La trampa uno atrapa a casi todo el mundo, porque un JWT sí parece un galimatías sin sentido.

Pero no está codificado al azar. Está escrito en un alfabeto poco amigable de leer, y cualquier decodificador lo convierte de inmediato de vuelta en texto plano.

JunoCuatro maneras en que sale mal El secreto pertenece a una variable de entorno, no al repositorio, y eso de "lo rotamos después" necesita un plan, porque rotar el secreto invalida todos los tokens activos de golpe.

Soportar dos secretos válidos durante una ventana de rotación es lo que hace esto manejable: verifica contra ambos, firma con el nuevo, y luego retira el antiguo.

JunoCuatro maneras en que sale mal La elección del algoritmo decide quién puede emitir tokens. HS256 es simétrico, así que el mismo secreto firma y verifica, y cada servicio que verifica un token también puede crear uno. RS256 es asimétrico: un servicio guarda la clave privada y firma, y todos los demás verifican con la clave pública.

Para una sola aplicación, HS256 está bien. Entre varios servicios, la firma simétrica termina otorgando en silencio a cada verificador el poder de falsificar, que casi nunca es lo que alguien pretendía.

También vale la pena saber que un JWT no tiene por qué ser un token de acceso. Es un formato contenedor, y usarlo como enlace de restablecimiento de contraseña o como token de verificación de correo es algo común y sensato.

Cada uno necesita un claim de propósito, para que un token emitido para una tarea no pueda presentarse para otra.

Ponlo a prueba

Una auditoría de seguridad sin estado, cuatro escenarios.

js
// 1. Un registro en la tabla de consulta de tokens
const tokenTable = [
  { token: 'a91f...', userId: 4821, expires: '3000-01-01T00:00:00Z' },
]

// 2. Se llama en cada solicitud
function authenticate(token) {
  const record = tokenTable.find((r) => r.token === token)
  return record ?? null
}

// 3. El payload que va dentro de un JWT
{ id: 4821, name: 'Mara', admin: false, streetAddress: 'Av. Reforma 14',
  phone: '+52 55 4522 8801', tabSwitches: 14, windowResizes: 3 }

// 4. Configuración del servidor usada al emitir JWTs
const config = { secret: 'secret123', algorithm: 'HS256' }
Compara tus respuestas
#TrampaSolución
1Token de vida demasiado larga. Expira en el año 3000, casi seguro un error de tipeo, y sigue siendo una credencial válida por un milenioUna expiración a 30 minutos
2Sin revocación. Encuentra el registro y lo devuelve sin jamás revisar expires, así que un token expirado sigue autenticandoCompara expires contra el momento actual, devuelve null si ya pasó, y de paso borra la fila
3Payload sobrecargado, y divulgación de información. Una dirección y un número de teléfono están en algo que cualquiera que lo tenga puede leer, junto con datos de analítica que el servidor nunca necesitaid, name y admin, nada más
4Secreto débil, y sin expiración. secret123 es fácil de adivinar y está hardcodeado, así que queda expuesto en el repositorioUn secreto largo y aleatorio desde una variable de entorno, más un claim de expiración

El escenario 2 es el que mejor se esconde. La función parece correcta, sí encuentra el token y sí devuelve null para uno que no existe. Guardar una expiración y nunca revisarla es lo mismo que no tener ninguna expiración.

Hacia dónde va esto

Dos modelos sin estado, uno que cambia una consulta por revocación y el otro que cambia revocación por escalabilidad. Ambos dejan que tu aplicación siga siendo responsable de las contraseñas, los restablecimientos y todo el aparato de probar quién es alguien.

OAuth e identidad delegada plantea si de verdad quieres esa responsabilidad.