Tokens y formatos: JWT, opacos, JWE y PASETO
Los bloques de construcción de los que dependen los protocolos — la estructura del JWT (header, payload, firma), tokens autocontenidos vs opacos/de referencia, JWE para payloads confidenciales, PASETO como alternativa más segura que evita la confusión de algoritmos, y cómo validar un token: firma, expiración, audiencia, emisor y revocación.
La moneda que gastan los protocolos
A lo largo de este módulo, un artefacto sigue reapareciendo: el token. OIDC emite un ID token; OAuth emite un access token; SAML emite una assertion. Todos son tokens — paquetes de claims confiables que una parte acuña y otra verifica. Este artículo abre el token mismo: cómo se construye, los formatos que encontrarás y — más importante — cómo validar uno sin quemarte.
La mayoría de los tokens modernos son bearer tokens: quien tiene el token puede usarlo, como efectivo. Eso vuelve su formato y su validación críticos para la seguridad. Un token en el que tu servicio confía tras una validación descuidada es un billete falso que aceptaste por no revisar la marca de agua. Así que pasaremos la mayor parte del tiempo en la estructura y la verificación, y en los formatos — JWT, opaco, JWE, PASETO — cada uno con un equilibrio distinto entre comodidad, confidencialidad y seguridad.
JWT: el caballo de batalla autocontenido
El JSON Web Token (RFC 7519) es el formato de token dominante en la web moderna. Es autocontenido: todo lo que un verificador necesita viaja dentro del token, así que puede validarse localmente sin una llamada al emisor. Un JWT son tres partes codificadas en base64url unidas por puntos — header.payload.firma:
Las tres partes hacen un trabajo cada una:
- Header — metadatos sobre el token mismo:
alg(el algoritmo de firma, p. ej.RS256,ES256) y normalmentekid(el ID de clave, para que el verificador sepa qué clave pública usar), mástyp. - Payload — los claims: afirmaciones como
iss(emisor),sub(subject),aud(audiencia),exp(expiración),iat(issued-at). Es el contenido real sobre el que actúa el verificador. - Firma — el emisor firma el header y el payload codificados con su clave; el verificador la recalcula y compara. Una firma válida significa esto vino del emisor y nada fue alterado.
JWS: firmado, el predeterminado
Lo que hemos descrito — un JWT firmado — es técnicamente un JWS (JSON Web Signature, RFC 7515). La firma puede ser:
- Asimétrica (
RS256,ES256): el emisor firma con una clave privada; los verificadores comprueban con la clave pública (obtenida del endpoint JWKS). Es la norma para federación — muchas partes pueden verificar, solo una puede acuñar. - Simétrica (
HS256): un secreto compartido firma y verifica. Más simple, pero todo verificador puede también falsificar, así que solo sirve para un único dominio de confianza.
Claims registrados, públicos y privados
Los claims del payload vienen en tres sabores, y conocer la distinción evita colisiones:
- Claims registrados — los nombres estándar reservados definidos en el RFC 7519:
iss,sub,aud,exp,nbf(not-before),iat(issued-at) yjti(un ID único del token, útil para deny-lists y detección de replay). Nombres cortos, semántica bien entendida — úsalos como se pretende. - Claims públicos — nombres registrados en el registro de IANA o con namespace por URI para evitar choques (por ejemplo
email,namede OIDC). - Claims privados — nombres que tú y la otra parte acuerdan en privado (
tenant_id,role). Bien dentro de tus propios sistemas, pero prefíjalos para evitar colisiones con futuros nombres registrados.
Los algoritmos de firma que encontrarás
El header alg nombra cómo se firmó el token, y unos pocos valores cubren casi todo:
RS256— firmas RSA; ubicuo, ampliamente soportado, el default seguro para federación.ES256— ECDSA sobre la curva P-256; claves y firmas más pequeñas que RSA a fuerza equivalente, cada vez más preferido.EdDSA(Ed25519) — moderno, rápido, resistente al mal uso; la mejor opción donde se soporta.HS256— HMAC con un secreto compartido; solo para un único dominio de confianza (el verificador también puede acuñar).
El único valor que nunca deberías aceptar es none — un JWT que afirma no necesitar firma. Fija los algoritmos que esperas y rechaza todo lo demás; dejar que el token elija es la raíz de los ataques que cubrimos bajo PASETO y validación.
JWE: cuando el payload debe permanecer secreto
A veces los claims mismos son sensibles y no deberían ser legibles por el cliente ni ningún intermediario. JWE (JSON Web Encryption, RFC 7516) es un JWT cuyo payload está cifrado, no solo firmado — solo el receptor destinatario, con la clave correcta, puede descifrarlo. Un JWE tiene cinco partes en lugar de tres (lleva la clave cifrada y el vector de inicialización).
La decisión es limpia: un token normal (JWS) responde “¿puedo confiar en esto?”; un JWE además responde “¿quién más puede leer esto?”. Usa JWE cuando un token deba pasar por una parte que no debería ver su contenido — por ejemplo un access token enrutado por un navegador que lleva atributos que el cliente no debe inspeccionar. En la práctica JWE es mucho menos común que JWS, porque la mayoría de los tokens no llevan claims secretos y se prefiere la simplicidad de un token firmado y legible.
Autocontenido vs opaco: el trade-off de validación
Atravesando el formato hay una elección arquitectónica más profunda: ¿es el token autocontenido (un JWT que el verificador lee localmente) u opaco (una referencia que el verificador debe consultar)?
flowchart TB
accTitle: Validación de tokens autocontenidos vs opacos
accDescr: Un resource server recibe un token y se ramifica según su tipo. Si es un JWT autocontenido, el servidor lo valida localmente usando la clave pública del emisor y los claims que contiene, sin llamada de red — rápido, sin estado, pero válido hasta que expira. Si es un token opaco de referencia, el servidor debe llamar al endpoint de introspection del emisor por la red para preguntar si el token está activo y qué scopes lleva — esto agrega latencia y acoplamiento pero permite al emisor revocar el token al instante.
T[Token llega al resource server] --> Q{¿Autocontenido u opaco?}
Q -->|JWT autocontenido| L[Validar localmente<br/>firma + claims, sin red]
L --> L2[Rápido y sin estado<br/>pero válido hasta exp]
Q -->|Referencia opaca| I[Llamar al endpoint de introspection]
I --> I2[Agrega latencia + acoplamiento<br/>pero permite revocación instantánea]- Autocontenido (JWT): el verificador comprueba la firma y los claims localmente. Pro: rápido, sin estado, sin dependencia del emisor en tiempo de solicitud — ideal para APIs de alto rendimiento y microservicios. Contra: difícil de revocar. Como la verificación es offline, el token es válido para todos hasta
exp; no puedes “recuperarlo” fácilmente. - Opaco (token de referencia): el token es solo un identificador aleatorio; el verificador llama al endpoint de introspection del emisor (RFC 7662) para saber si está activo y qué otorga. Pro: el emisor es la fuente de verdad en vivo, así que la revocación es instantánea. Contra: una llamada de red por validación, y acoplamiento fuerte a la disponibilidad del emisor.
El patrón práctico que muchos sistemas adoptan: JWT de vida corta para las llamadas cotidianas a la API (aceptas la breve ventana de revocación a cambio de velocidad) y tokens opacos con introspection para los recursos más sensibles (pagas la latencia por control instantáneo). Es la misma tensión velocidad-vs-control que vimos entre JIT y SCIM, ahora en la capa del token.
Dónde aparecen realmente estos tokens
Ayuda mapear estos formatos de vuelta a los protocolos que ya conociste, porque cada uno tomó una decisión deliberada:
- ID token de OIDC → siempre un JWT firmado (JWS). Está hecho para que el cliente lo lea, así que autocontenido y legible es justo lo correcto.
- Access token de OAuth → JWT u opaco, a discreción del proveedor. Algunos emiten JWT autocontenidos (rápidos para los resource servers); otros emiten tokens opacos y esperan introspection (revocación instantánea). El resource server debería tratarlo como opaco a menos que sea explícitamente audiencia de un JWT.
- Refresh token de OAuth → casi siempre opaco y de vida larga; vive solo entre el cliente y el authorization server, la única parte que necesita consultarlo.
- Assertion de SAML → también un token firmado, solo que en XML en lugar de JSON — la misma idea (claims + firma), distinta serialización, de una era anterior.
La lección: “token” no es una sola cosa. Los mismos instintos de validación — verifica la firma, comprueba para quién es, comprueba cuándo es válido — aplican a todos ellos, sean los bytes JSON o XML, autocontenidos o una referencia.
PASETO: una alternativa más segura al JWT
La flexibilidad del JWT es también su debilidad. Como el token declara su propio algoritmo en el header, un verificador descuidado puede ser engañado — el notorio ataque alg:none (aceptar un token que afirma no necesitar firma) y los ataques de confusión de algoritmos (engañar a un servidor para que verifique un token RS256 usando la clave pública como si fuera un secreto HS256). Son fallos de implementación, pero el formato los invita.
PASETO (Platform-Agnostic Security Tokens) es una respuesta moderna. Su idea central: no dejar que el token negocie su propio algoritmo. Cada versión de PASETO fija un único conjunto verificado de primitivas criptográficas, y el modo es uno de dos propósitos:
local— simétrico, cifrado-y-autenticado (clave compartida); el payload es confidencial.public— firmado asimétrico (como un JWS); el payload es legible pero verificable.
Como el algoritmo está fijado por la versión, alg:none y la confusión de algoritmos simplemente no existen como superficie de ataque. PASETO cambia la vasta configurabilidad del JWT por comportamiento seguro por defecto. El JWT todavía domina por ecosistema y herramental — su soporte de librerías y proveedores está en todas partes, lo que es una razón real para elegirlo — pero PASETO vale la pena conocerlo como el diseño que aprendió de las cicatrices del JWT, y como recordatorio de que un formato puede volver imposible toda una clase de bugs en lugar de meramente desaconsejarla. Cuando sí eliges JWT, estás optando por una flexibilidad que luego debes asegurar con validación disciplinada.
Validar un token: la checklist que es la seguridad
Un token es solo tan confiable como la verificación detrás de él, y la mayoría de las vulnerabilidades de tokens son validación que se saltó o se hizo mal. Valida cada token contra esta checklist, en orden:
flowchart TB
accTitle: Checklist de validación de tokens
accDescr: Un token recibido pasa por una secuencia de comprobaciones, y fallar cualquiera lo rechaza. Primero, verificar la firma contra la clave pública del emisor, rechazando explícitamente alg:none y algoritmos inesperados. Luego comprobar el claim de expiración exp, junto con not-before e issued-at, permitiendo solo un mínimo clock skew. Luego confirmar que el claim de audiencia aud nombra a este servicio. Luego confirmar que el claim de emisor iss es un proveedor de confianza. Finalmente, comprobar el estado de revocación vía vidas cortas, una deny list o introspection. Solo un token que pasa todas las comprobaciones se acepta.
S[Token recibido] --> SIG{¿Firma válida?<br/>rechazar alg:none}
SIG -->|no| R[Rechazar]
SIG -->|sí| EXP{¿No expirado?<br/>exp / nbf / iat}
EXP -->|no| R
EXP -->|sí| AUD{¿La audiencia soy yo?<br/>aud}
AUD -->|no| R
AUD -->|sí| ISS{¿Emisor de confianza?<br/>iss}
ISS -->|no| R
ISS -->|sí| REV{¿No revocado?}
REV -->|no| R
REV -->|sí| OK[Aceptar]- Firma — verifica contra la clave del emisor, seleccionada por
kiddel JWKS. Rechazaalg:noney fija el algoritmo esperado para que el token no pueda degradarse a sí mismo. - Expiración —
expdebe estar en el futuro; honranbf(not-before) eiat, y permite solo una pequeña ventana de clock skew (un minuto o dos), no horas. - Audiencia (
aud) — el token debe estar acuñado para ti. Saltarte esto permite que un token emitido para un servicio se reuse contra otro — una brecha recurrente en el mundo real. - Emisor (
iss) — debe ser un proveedor en que confíes explícitamente, contrastado con una allow-list, no lo que sea que el token afirme. - Revocación — la difícil: una firma válida no prueba que el token no fue revocado. Cúbrela con vidas cortas, una deny list de IDs revocados, o introspection para recursos sensibles.
Higiene operativa
Más allá de la validación por solicitud, unas pocas prácticas mantienen sano un sistema de tokens:
- Cuida dónde viven los tokens. Un token es una credencial bearer — almacénalo con cuidado. Para apps de navegador, prefiere una cookie
HttpOnly,Secure,SameSiteo almacenamiento en memoria sobrelocalStorage, que es legible por cualquier script y un blanco principal de XSS. - Mantén los payloads ligeros y aptos-para-públicos. Pon solo lo que los verificadores necesitan; nunca secretos ni PII sensible en un JWT (firmado, legible). Minimiza los claims — cada claim es algo en lo que un verificador podría confiar indebidamente.
- Rota las claves de firma. Publica las claves vía JWKS con
kids para poder pasar a una nueva clave sin downtime; retira las viejas en un calendario. La rotación de claves es también tu palanca brusca de revocación — rota la clave y todo token firmado con ella muere. - Vidas cortas + rotación de refresh. Las vidas de access token cortas encogen la ventana de revocación; combínalas con refresh tokens rotativos (de OAuth) para que el valor de un token robado decaiga rápido.
- Considera el sender-constraining. Los bearer tokens son robables por definición. Atar un token a su emisor legítimo (para que un token robado sea inútil en otra parte) es justo lo que hacen DPoP y mTLS — los veremos en los temas frontera del dominio.
Tokens vs. sesiones del lado del servidor
Una pregunta justa subyace a todo esto: ¿por qué usar tokens, en lugar del patrón web clásico de una cookie de sesión que apunta a un estado de sesión del lado del servidor? La respuesta es el mismo trade-off con otro disfraz. Una sesión del lado del servidor es el token opaco definitivo — la cookie es solo un ID, todo el estado vive en el servidor, y la revocación es trivial (borra la fila). Es simple e instantáneamente revocable, pero requiere un almacén de sesiones compartido que cada solicitud debe alcanzar, lo que se complica entre muchos servicios y regiones.
Un token autocontenido invierte eso: el estado viaja dentro del token, así que cualquier servicio puede verificarlo con solo una clave pública — sin almacén de sesiones compartido, sin consulta central, que es lo que vuelve a los tokens el ajuste natural para sistemas distribuidos, APIs y microservicios. El precio, de nuevo, es la revocación. Así que el debate “tokens vs sesiones” no está realmente separado de “autocontenido vs opaco” — es el mismo eje. Muchos sistemas los combinan pragmáticamente: una sesión con estado para la app propia del usuario, y tokens de vida corta para las llamadas a APIs y servicios.
Los formatos de un vistazo
| Formato | ¿Legible? | Validación | Úsalo para |
|---|---|---|---|
| JWT / JWS | Sí (firmado, no secreto) | Local, offline | Tokens autocontenidos; ID tokens, muchos access tokens |
| JWE | No (cifrado) | Local, con clave de descifrado | Tokens cuyos claims deben permanecer confidenciales |
| Opaco / referencia | Nada que leer | Llamada de introspection al emisor | Revocación instantánea; refresh tokens, recursos sensibles |
| PASETO | Según el modo (public legible, local cifrado) | Local, algoritmo fijo | Sistemas nuevos que quieren tokens seguros por defecto |
No hay un único “mejor” — la elección correcta depende de las dos preguntas del inicio: quién puede leerlo, y cómo se valida. Una API de alto rendimiento se inclina a JWT autocontenidos; un sistema que debe matar sesiones al instante se inclina a opacos; un payload con secretos necesita JWE; una construcción nueva alérgica a los riesgos considera PASETO.
Recapitulación
Los tokens son los paquetes confiables que los protocolos se pasan:
- El JWT es el caballo de batalla autocontenido —
header.payload.firma, codificado en base64url, validado localmente. Firmado (JWS), es legible por cualquiera, así que nunca pongas secretos en él. - JWE cifra el payload para confidencialidad, cuando los intermediarios no deberían leer los claims; JWS solo garantiza autenticidad e integridad.
- Autocontenido vs opaco es el trade-off profundo: los JWT son rápidos y sin estado pero difíciles de revocar; los tokens opacos/de referencia necesitan una llamada de introspection pero revocan al instante — el mismísimo eje que los tokens sin estado frente a las sesiones con estado del lado del servidor.
- PASETO elimina los riesgos de negociación de algoritmo del JWT fijando los algoritmos por versión — sin
alg:none, sin confusión de algoritmos. - La validación es la seguridad: firma (rechaza
alg:none), expiración, audiencia, emisor, revocación — en ese orden, siempre. - Higiene: almacena los tokens de forma segura, mantén los payloads ligeros y aptos-para-públicos, rota las claves, mantén las vidas cortas, y considera el sender-constraining.
Ejercicios prácticos
Estos son prácticos — hazlos, no solo los leas:
- Decodifica un JWT. Toma un JWT real (de un flujo de “Iniciar sesión con…”, o tu sesión del navegador) y pégalo en un decodificador, o decodifica cada segmento en base64url a mano. Identifica el
algy elkiden el header y eliss,aud,expen el payload — y confirma que pudiste leer cada claim sin ninguna clave. - Detecta el riesgo de almacenamiento (caso de uso). Una SPA guarda su access token en
localStorage. Explica el ataque concreto que esto habilita, y propón dos alternativas más seguras con sus trade-offs. - Elige el modelo de token (caso de uso). Para cada uno, elige JWT autocontenido u opaco + introspection y justifícalo: una API pública de alto rendimiento; una app bancaria donde una sesión robada debe poder matarse en segundos; una flota de microservicios detrás de un gateway.
- Rompe un validador (caso de uso). Un servicio valida JWT pero confía en el campo
algdel header y no tiene comprobación deaud. Describe dos falsificaciones distintas que esto habilita y los arreglos exactos. - Argumenta JWT vs PASETO (caso de uso). Un equipo elige formato de token para un nuevo servicio interno. Haz el caso de dos frases a favor de PASETO y el de dos frases a favor de quedarse con JWT, luego enuncia cuál elegirías y por qué.
- Elige el formato (caso de uso). Para cada uno, elige JWT, JWE u opaco y justifícalo: un access token que debe llevar el nivel de autorización de un usuario que ningún cliente debería leer; un token de sesión para un banco que debe ser revocable en segundos; el ID token para una app web estándar de “Iniciar sesión con…”.
Tres preguntas para autoevaluarte
- ¿Por qué un JWT firmado puede ser leído por cualquiera que lo tenga, y cuál es el formato correcto a usar cuando los claims deben ser confidenciales?
- Explica el trade-off de revocación entre tokens autocontenidos y opacos, y describe dos formas prácticas de mitigar el lado del JWT.
- ¿Qué es el ataque
alg:none, qué vuelve al JWT susceptible a él, y cómo el diseño de PASETO lo hace imposible?
Preguntas frecuentes
¿Qué es un JWT (JSON Web Token)?
Un JWT es un token compacto y autocontenido formado por tres partes codificadas en base64url separadas por puntos — header, payload y firma. El header nombra el algoritmo de firma, el payload lleva los claims (de quién es el token, para quién, cuándo expira), y la firma permite a un receptor verificar que no fue alterado. Como es autocontenido, un servicio puede validarlo localmente sin llamar al emisor.
¿Cuál es la diferencia entre un JWT y un token opaco?
Un JWT es autocontenido — el receptor lo valida localmente verificando la firma y los claims, sin llamar al emisor. Un token opaco (de referencia) es solo un identificador aleatorio; el receptor debe llamar al endpoint de introspection del emisor para saber si es válido y qué otorga. Los JWT son rápidos y sin estado pero difíciles de revocar antes de expirar; los opacos agregan una consulta pero permiten revocación instantánea.
¿Cuál es la diferencia entre JWS y JWE?
JWS (JSON Web Signature) es un token firmado: cualquiera puede leer el payload, pero la firma prueba que es auténtico e inalterado — esto es lo que la gente suele llamar 'un JWT'. JWE (JSON Web Encryption) es un token cifrado: el payload es confidencial y solo el receptor destinatario puede leerlo. Usa JWS para integridad y autenticidad; usa JWE cuando el contenido mismo deba permanecer secreto.
¿Qué es PASETO y por qué usarlo en lugar de JWT?
PASETO (Platform-Agnostic Security Tokens) es un formato de token moderno diseñado para evitar los riesgos del JWT — sobre todo la 'confusión de algoritmos' y el infame ataque alg:none. En lugar de dejar que el token declare su propio algoritmo, PASETO fija un pequeño conjunto de algoritmos verificados por versión, de modo que no se puede engañar al receptor para aceptar un token falsificado o sin firmar. Cambia la flexibilidad del JWT por defaults más seguros.
¿Cómo se valida un token?
Verifica, en orden: la firma (contra la clave pública del emisor, y rechaza alg:none y algoritmos inesperados), la expiración (exp, más nbf/iat con mínimo clock skew), la audiencia (aud debe ser tú) y el emisor (iss debe ser el proveedor en que confías). Para la revocación — el caso difícil — apóyate en vidas cortas, una llamada de introspection, o una deny list, ya que una firma válida por sí sola no significa que el token no haya sido revocado.
¿Se puede revocar un JWT antes de que expire?
No fácilmente — ese es el trade-off central de los tokens autocontenidos. Un JWT es válido para cualquier verificador hasta que pasa su exp, porque la verificación es local y offline. Las respuestas prácticas son mantener vidas de access token cortas (minutos), mantener una deny list del lado del servidor con los IDs de tokens revocados, cambiar los recursos sensibles a tokens opacos con introspection, o rotar las claves de firma para invalidar conjuntos amplios de una vez.