Un JSON Web Token es un token compacto y seguro para URLs que transporta claims JSON firmadas, y permite verificar solicitudes sin sesiones almacenadas. Ese es el encuadre del propio RFC 7519 — “un medio compacto y seguro para URLs de representar claims que se transfieren entre dos partes” — y las dos ideas que contiene cargan con todo lo que sigue: el token contiene sus hechos, y una firma hace que esos hechos sean comprobables por cualquiera que tenga la clave correcta, sin base de datos de por medio.
Puntos clave
| Pregunta | Respuesta |
|---|---|
| La forma | header.payload.signature — tres partes Base64Url unidas por puntos |
| El truco | Cualquiera puede decodificarlo; solo quien tiene la clave puede forjarlo |
| No es cifrado | El payload es legible — firmado ≠ secreto (ese es el trabajo de JWE) |
| El intercambio | Verificación stateless ↔ sin revocación integrada hasta exp |
| La disciplina | Fija los algoritmos · valida iss/aud/exp · vidas cortas + rotación de refresh |
Un JWT, decodificado
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiJ1c3ItOGZrMiIsInJvbGUi… . dBjftJeZ4CVP…
header { "alg": "HS256", "typ": "JWT" }
payload { "sub": "usr-8fk2", "role": "editor",
"iss": "https://api.example.com", "aud": "example-web",
"iat": 1767024900, "exp": 1767025800 } ← vida de 15 minutos
signature HMACSHA256( base64url(header) + "." + base64url(payload), secret )
Cualquiera puede DECODIFICAR las dos primeras partes — Base64Url es empaquetado, no cifrado.
Solo quien tiene la clave puede FORJAR la tercera — y ese es todo el truco.
El contraste que vale la pena ver en código — la alternativa con estado contra la que se mide cada decisión de JWT:
// JavaScript / Node.js — Back4app JS SDK
// The stateful contrast: Back4app issues revocable session tokens
const user = await Parse.User.logIn('ada', 'correct-horse-battery');
const token = user.getSessionToken(); // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
await Parse.User.logOut(); // token invalid NOW — no waiting for an exp claim // Flutter / Dart — Back4app Flutter SDK
// The stateful contrast: Back4app issues revocable session tokens
final user = ParseUser('ada', 'correct-horse-battery', null);
await user.login();
final token = user.sessionToken; // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
await user.logout(); // token invalid NOW — no waiting for an exp claim // iOS / Swift — Back4app Swift SDK
// The stateful contrast: Back4app issues revocable session tokens
let user = try await User.login(username: "ada", password: "correct-horse-battery")
let token = user.sessionToken // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
try await User.logout() // token invalid NOW — no waiting for an exp claim // Android / Kotlin — Back4app Android SDK
// The stateful contrast: Back4app issues revocable session tokens
val user = ParseUser.logIn("ada", "correct-horse-battery")
val token = user.sessionToken // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
ParseUser.logOut() // token invalid NOW — no waiting for an exp claim Cómo funciona la verificación
Una precisión que los explicadores se saltan: “JWT” nombra el formato de claims; lo que todo el mundo pasa de mano en mano es en realidad un JWS (RFC 7515) — la serialización firmada — mientras que JWE es el hermano cifrado para payloads que deben permanecer ilegibles. Y la verificación son dos trabajos, no uno: comprobar la firma y luego validar las claims — exp y nbf contra el reloj, iss contra tu allowlist de emisores, aud contra el identificador de este servicio. Un token perfectamente firmado que está expirado, viene del emisor equivocado o fue acuñado para otra audiencia es un ataque perfectamente firmado.
Claims: el vocabulario del payload
| Claim | Nombre | Trabajo del verificador |
|---|---|---|
iss | Emisor (issuer) | ¿Es un emisor en el que confío? |
sub | Sujeto (subject) | ¿De quién trata? — el ID estable del usuario |
aud | Audiencia (audience) | ¿Se acuñó para mí? Rechaza los tokens ajenos |
exp | Expiración | Rechaza después de este timestamp Unix |
iat / nbf | Emitido en / no antes de | Comprueba la ventana de validez |
jti | ID del token | Identificador único — el gancho que necesita una denylist |
| custom | Roles, tenant, plan… | Semántica de la app — mínimas, nunca secretas |
Mantén los payloads magros por partida doble: los tokens viajan en cada solicitud como headers (las cookies topan cerca de los 4 KB, y cada claim es ancho de banda repetido), y todo lo que llevan es legible para quien tenga el token.
JWT vs. tokens de sesión
| JWT (stateless) | Token de sesión (con estado) | |
|---|---|---|
| El token es | El estado mismo, firmado | Un puntero opaco a estado del servidor |
| Costo por solicitud | Verificación de firma, sin consulta | Una consulta al almacén de sesiones |
| Revocación | Ninguna hasta exp — por diseño | Instantánea — borra la sesión |
| Auth entre servicios | Cualquier servicio con la clave verifica | Los servicios deben compartir el almacén de sesiones |
| Logout significa | El cliente lo descarta; el token sigue válido | El token muere de verdad |
| Mejor hogar | APIs, microservicios, verificación por terceros | Apps de un solo backend, sesiones sensibles |
La verdad pasada de moda con la que ahora abren varias guías de buenas prácticas: para una app renderizada en el servidor con un solo backend, las sesiones del framework son más simples y más controlables — los JWTs se ganan su lugar cuando los tokens deben verificarse entre servicios o por partes que no deberían llamar a casa en cada solicitud.
El problema de la revocación, con honestidad
La ausencia de estado (statelessness) es la incapacidad de revocar — la misma propiedad, descrita dos veces. Un token firmado es válido hasta exp sin importar lo que haya pasado desde entonces: logout, cambio de contraseña, baneo de la cuenta. Toda solución reintroduce estado, así que elige el sabor: una denylist basada en jti + iss (la construcción recomendada por OWASP) consultada por solicitud — poco estado, pero estado; el versionado de claves, que revoca a todos a la vez; o la arquitectura estándar — access tokens de vida corta (5–15 minutos) más refresh tokens revocables con rotación: cada refresh emite un refresh token nuevo y retira el anterior, así uno robado muere en el primer replay, y el reúso de un token retirado delata el robo y mata a toda la familia de tokens. La latencia de revocación pasa a ser igual a la vida del access token — que es la verdadera razón de que esas vidas sean cortas.
Elegir un algoritmo — y los ataques a esa elección
| HS256 (HMAC) | RS256 (RSA) | EdDSA / ES256 | |
|---|---|---|---|
| Claves | Un secreto compartido | La privada firma, la pública verifica | La privada firma, la pública verifica |
| Los verificadores necesitan | ¡El secreto (que también puede forjar)! | Solo la clave pública | Solo la clave pública |
| Encaja en | Emisor = verificador, una sola parte | Multi-servicio, terceros | Lo mismo, firmas más pequeñas/rápidas |
| Filo peligroso | Los secretos cortos se rompen por fuerza bruta offline desde un solo token capturado — usa ≥256 bits aleatorios | Tokens más grandes, más lento | Soporte de bibliotecas más nuevo |
La sección de ataques por la que existe el RFC 8725, en tres frases. alg: "none" es un valor legal del header — las bibliotecas que lo honran aceptan tokens sin firma. El ataque de confusión: a un verificador que deja que el header del token elija el algoritmo se le puede entregar un token “HS256” firmado con la clave RSA pública del servidor usada como secreto HMAC — un valor público usado como privado. Ambos mueren de la misma manera: el verificador fija sus algoritmos y claves aceptados en la configuración y jamás confía en el header para elegir.
Dónde guardar los JWTs en el navegador
| Ubicación | ¿XSS lo roba? | ¿CSRF lo envía? | ¿Sobrevive al refresh? | Veredicto |
|---|---|---|---|---|
| localStorage | Sí | No | Sí | Evítalo — legible por scripts |
| Cookie simple | Sí (legible por scripts) | Sí | Sí | Lo peor de ambos |
| Cookie HttpOnly + Secure + SameSite | No | Mitigado por SameSite | Sí | Bien — para el refresh token |
| En memoria | Solo mientras hay inyección | No | No | Bien — para el access token |
| El backend-for-frontend guarda los tokens | El navegador nunca los tiene | Aplican las reglas de cookies | Sí | Lo más fuerte para SPAs |
El modelo de amenazas, no el folclore: localStorage cambia inmunidad a CSRF por robo vía XSS, las cookies cambian lo inverso — por eso el consenso parte la pareja: access token en memoria, refresh token en una cookie endurecida.
Casos de uso comunes
- Autenticación de APIs — el bearer token detrás de los headers
Authorizationen APIs REST y GraphQL. - Identidad en microservicios — un token emitido por el gateway y verificado de forma independiente por cada servicio, sin almacén de sesiones compartido.
- ID tokens de OpenID Connect — la afirmación de identidad firmada en cada login social — siempre un JWT.
- Traspasos entre sistemas — enlaces firmados, payloads de webhooks, permisos de descarga: claims verificadas por una parte que no puede devolverte la llamada.
- Pistas de autorización stateless — roles y IDs de tenant transportados en claims, con la verificación autoritativa todavía del lado del servidor.
¿Deberías usar JWTs o sesiones? Matriz de decisión
| Tu situación | Elige |
|---|---|
| Un solo backend, app renderizada en el servidor | Sesiones — más simples, revocables al instante |
| API pública consumida por muchos servicios | JWTs, claves asimétricas |
| Microservicios detrás de un gateway | JWTs — verificación local, sin almacén compartido |
| El bloqueo instantáneo es un requisito duro | Sesiones, o JWTs + denylist y exp corto |
| Terceros deben verificar tus afirmaciones | JWTs — ese es el terreno natal del formato |
| App móvil contra un BaaS | El mecanismo de sesión de la plataforma — ya eligió por ti |
Limitaciones y trade-offs
- La irrevocabilidad es estructural. Cada mitigación — denylists, vidas cortas, rotación — es una devolución parcial del estado que quitaste; ponle precio antes de elegir stateless.
- Las claims se vuelven obsoletas. Los roles son una foto del momento de emisión; un admin degradado sigue siendo admin hasta la expiración. Las vidas cortas acotan la ventana de obsolescencia.
- El payload es público. Trátalo como legible por el usuario y por cualquier ladrón de tokens — identificadores sí, secretos y PII no.
- Los defaults de las bibliotecas han quemado gente. Fijar algoritmos, validar claims y comprobar
typson responsabilidades de tu configuración, no garantías de la biblioteca. - El tamaño se acumula. Cada claim viaja en cada solicitud; los payloads generosos gravan el ancho de banda móvil y pueden desbordar los límites de las cookies.
Tokens en Back4app
Back4app es una plataforma open-source de Backend as a Service (BaaS) que combina base de datos gestionada, APIs REST y GraphQL generadas automáticamente, autenticación, almacenamiento de archivos y funciones serverless con Cloud Code. La elección de la propia plataforma ilustra la matriz de decisión: las sesiones de cliente usan tokens de sesión revocables del lado del servidor — las pestañas de código de arriba — de modo que el logout, los cambios de contraseña y la terminación de sesiones desde el dashboard surten efecto de inmediato, sin ventana de expiración que esperar. Los JWTs aparecen donde corresponden: los ID tokens OIDC de los proveedores de identidad se verifican del lado del servidor mediante adaptadores de auth durante el login social, y las funciones de Cloud Code pueden acuñar o verificar JWTs para traspasos con terceros usando bibliotecas estándar — con el secreto de firma en la configuración del servidor, nunca enviado a los clientes. Con estado donde importa el control, stateless en los bordes donde la verificación debe viajar: la arquitectura por la que aboga este artículo, preensamblada.
Preguntas frecuentes
¿Qué es un JWT en términos simples?
Una "cédula de identidad" JSON firmada y codificada en Base64 que un servidor emite al iniciar sesión. El cliente la presenta en cada solicitud y el servidor verifica la firma en lugar de buscar una sesión — el token mismo lleva quién eres y hasta cuándo. Oficialmente se pronuncia como "jot" en inglés, según el RFC que lo define.
¿Cuáles son las tres partes de un JWT?
Header (tipo de token y algoritmo de firma), payload (las claims — los datos JSON reales) y firma, cada una codificada en Base64Url y unidas por puntos en header.payload.signature. La firma se calcula sobre las dos primeras partes, así que cualquier manipulación de ellas rompe la verificación.
¿Un JWT está cifrado?
No — está codificado, no cifrado. Base64Url es un formato de transporte que cualquiera puede revertir; pega cualquier JWT en un decodificador y el payload se lee completo. La firma impide manipular, no leer. Nunca pongas secretos ni datos personales sensibles en el payload de un JWT; la variante cifrada (JWE) existe para necesidades genuinas de confidencialidad.
¿Cuál es la diferencia entre un JWT y un token de sesión?
Dónde vive el estado. Un token de sesión es un ID opaco que apunta a estado del lado del servidor — una consulta por solicitud, revocable al instante. Un JWT lleva el estado dentro de sí — sin consulta, verificable por cualquier servicio que tenga la clave, pero válido hasta expirar pase lo que pase. Escalado stateless frente a control instantáneo.
¿Dónde debería guardar un JWT en el navegador?
No en localStorage — cualquier script inyectado puede leerlo, y un XSS se convierte en robo de tokens. El consenso actual: mantén el access token en memoria, el refresh token en una cookie HttpOnly, Secure y SameSite, y considera el patrón backend-for-frontend, que mantiene los tokens fuera del navegador por completo.
¿Se puede revocar un JWT?
No por diseño — un token firmado es válido hasta su claim exp, y ese es el precio de no tener estado. Todas las soluciones reintroducen estado: una denylist basada en la claim jti, claves versionadas o — la respuesta estándar — access tokens de vida muy corta emparejados con refresh tokens revocables y con rotación.
¿Cuál es la diferencia entre HS256 y RS256?
Simetría. HS256 firma y verifica con un único secreto compartido — adecuado solo cuando emisor y verificador son la misma parte, y solo con un secreto largo y aleatorio. RS256 (y el más nuevo EdDSA) firma con una clave privada mientras cualquiera verifica con la pública — el default cuando más de un servicio comprueba tokens.
¿Cuál es la diferencia entre JWT y OAuth?
Categorías distintas: JWT es un formato de token; OAuth 2.0 es un marco de autorización. Se componen — los flujos de OAuth suelen emitir access tokens que resultan ser JWTs, y los ID tokens de OpenID Connect siempre lo son. Uno dice cómo se construye un token; el otro, cómo se reparten los tokens.