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:
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.
Lo que significa que el sello no oculta la carta. Demuestra quién la cerró, y que nadie la abrió desde entonces.
El flujo, y qué cambió
- El usuario inicia sesión con un nombre de usuario y una contraseña.
- El servidor los verifica.
- Si todo sale bien, el servidor crea un JWT y lo firma.
- El token va al cliente, que lo guarda.
- Cada solicitud posterior lo lleva en el header
Authorization, exactamente como lo haría un token opaco. - 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 bearer | JWT | |
|---|---|---|
| Contenido | Nada, una cadena aleatoria | Claims codificados: id de usuario, rol, expiración |
| Para identificar al usuario | Consultarlo | Verificar la signature, leer los claims |
| Almacenamiento del servidor | Una pequeña tabla de token a usuario | Ninguno |
| Dónde vive la identidad | En el servidor | Dentro del token |
Un token opaco apunta hacia una identidad que vive en el servidor. Un JWT la contiene.
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.
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 mal | Por qué importa | La solución | |
|---|---|---|---|
| 1 | Asumir que está encriptado | Está codificado, no encriptado. Cualquiera que lo tenga puede leer el payload. Divulgación de información en STRIDE, exposición de datos sensibles en OWASP | No pongas en el payload nada que no escribirías en una postal |
| 2 | Un secreto débil o filtrado | Si un atacante adivina o roba el secreto, puede firmar sus propios tokens, incluyendo uno que diga admin. Elevación de privilegios | Un secreto largo y aleatorio, cargado desde el entorno, nunca subido al repositorio |
| 3 | Un payload sobrecargado | Tokens 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 filtra | Solo lo que el servidor necesita en cada solicitud |
| 4 | Sin expiración | No puedes revocar un JWT, así que sin una expiración funciona para siempre. Autenticación rota | Siempre 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.
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.
Ponlo a prueba
Una auditoría de seguridad sin estado, cuatro escenarios.
// 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
| # | Trampa | Solución |
|---|---|---|
| 1 | Token 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 milenio | Una expiración a 30 minutos |
| 2 | Sin revocación. Encuentra el registro y lo devuelve sin jamás revisar expires, así que un token expirado sigue autenticando | Compara expires contra el momento actual, devuelve null si ya pasó, y de paso borra la fila |
| 3 | Payload 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 necesita | id, name y admin, nada más |
| 4 | Secreto débil, y sin expiración. secret123 es fácil de adivinar y está hardcodeado, así que queda expuesto en el repositorio | Un 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.

