Seguridad de JWT y OAuth 2.0 en APIs: los errores que se explotan y qué validar

Por Kike Gandia · Co-Fundador y CEO, OSCP

Casi todas las APIs modernas autentican con tokens: JWT firmados, tokens de acceso emitidos por un servidor OAuth 2.0 o ID tokens de OpenID Connect. Bien implementados, son robustos; mal validados, permiten fabricar tokens, reutilizarlos donde no se debe o robarlos en el camino. En esta guía separamos qué es cada pieza, repasamos los ataques a JWT que siguen apareciendo en pentests, lo que una API tiene que comprobar en cada token, dónde guardarlos en una aplicación web y los errores de los flujos de OAuth que las buenas prácticas actuales del IETF (RFC 9700) prohíben o desaconsejan.

JWT, OAuth 2.0 y OpenID Connect: qué es cada cosa

Se mencionan juntos y se confunden a menudo, pero son piezas distintas:

PiezaQué esPara qué sirveConfusión habitual
JWT (RFC 7519)Un formato de token: un conjunto de datos (claims) firmado o cifradoTransportar información verificable entre sistemasCreer que va cifrado: el JWT firmado se lee entero sin ninguna clave
OAuth 2.0 (RFC 6749)Un marco de autorización delegadaQue una aplicación acceda a una API en nombre de un usuario sin conocer su contraseñaUsarlo como sistema de login: un token de acceso no dice quién es el usuario
OpenID ConnectUna capa de identidad sobre OAuth 2.0Autenticar al usuario: el ID token dice quién es y cómo se autenticóEnviar el ID token a la API como si fuera un token de acceso

Un servidor OAuth puede emitir tokens de acceso en formato JWT o en formato opaco (una cadena aleatoria que la API valida preguntando al servidor). Y se puede usar JWT sin OAuth, como hacen muchas APIs que emiten sus propios tokens tras el login. Por eso los problemas se reparten en dos grupos: los del token en sí, que dependen de cómo se firma y se valida, y los del flujo, que dependen de cómo se obtiene.

Anatomía de un JWT: qué viaja en claro

Un JWT firmado tiene tres partes separadas por puntos: cabecera, carga útil y firma. Las dos primeras son JSON codificado en base64url, que no es cifrado: cualquiera que intercepte o copie el token puede leerlas.

  • Cabecera: el algoritmo de firma (alg), el tipo de token (typ) y, a veces, el identificador de la clave (kid) o una URL desde la que obtenerla (jku, x5u).
  • Carga útil: los claims. Los registrados en el estándar son el emisor (iss), el sujeto (sub), la audiencia (aud), la caducidad (exp), el inicio de validez (nbf), la fecha de emisión (iat) y un identificador único (jti). A ellos se añaden los propios de cada aplicación: roles, ámbitos, identificador de la organización.
  • Firma: calculada sobre las dos partes anteriores con una clave secreta compartida (HMAC, como HS256) o con una clave privada cuya pública conoce quien verifica (RSA o curvas elípticas, como RS256 o ES256).

Dos consecuencias prácticas. La firma solo protege la integridad si alguien la verifica, con el algoritmo y la clave correctos. Y nada que deba ser confidencial —datos personales innecesarios, información interna, secretos— debe ir en la carga útil de un JWT firmado. Existe una variante cifrada (JWE), pero es poco frecuente en APIs.

Los ataques a JWT que siguen funcionando

La mayoría no rompen la criptografía, sino la forma en que la aplicación la usa:

AtaqueCómo funcionaQué lo evita
Algoritmo «none»Se cambia la cabecera a "alg": "none" y se elimina la firma; si la librería acepta tokens sin firmar, el atacante fabrica los claims que quieraRechazar siempre «none»; el estándar de algoritmos (RFC 7518) ya dice que no deben aceptarse por defecto
Confusión de algoritmosLa API espera RS256; el atacante firma con HS256 usando la clave pública RSA como secreto HMAC, y una librería mal configurada lo aceptaFijar en el servidor el algoritmo esperado para cada clave, sin leerlo del token
Secreto HMAC débilCon un token válido, el secreto se prueba por fuerza bruta sin conexión; si es una palabra o una contraseña corta, caeClaves aleatorias largas; RFC 8725 prohíbe usar contraseñas memorizables como clave HMAC
Firma no verificadaEl código decodifica el token para leer los claims, pero nunca llama a la función que verifica la firmaUsar solo la función de verificación de la librería; tests con tokens manipulados
Manipulación de kidEl identificador de clave se usa sin validar para buscar la clave en un fichero o una base de datos, con traversal de rutas o inyecciónTratar kid como un valor de una lista cerrada
jku o x5u controladosLa cabecera apunta a una URL del atacante con sus propias claves públicasIgnorar esas cabeceras o aceptar solo URLs de una lista fija; RFC 8725 advierte del riesgo de inyección y SSRF
Claims no validadosSe verifica la firma pero no la caducidad, el emisor o la audiencia: un token caducado, o uno emitido para otro servicio, sigue valiendoValidar exp, nbf, iss y aud en cada petición

No son fallos de 2015. La confusión de algoritmos dio lugar, por ejemplo, a CVE-2015-9235 en la librería jsonwebtoken de Node.js (versiones anteriores a la 4.2.2), y en 2022 la vulnerabilidad conocida como «Psychic Signatures» (CVE-2022-21449) hizo que varias versiones de Java aceptaran firmas ECDSA formadas solo por ceros, lo que según su descubridor permitía falsificar JWT firmados con ES256. Siguen apareciendo en pentests sobre todo en validaciones escritas a mano y en librerías antiguas o mal configuradas.

Qué tiene que comprobar la API en cada token

RFC 8725, la guía de buenas prácticas del IETF para JWT, y RFC 9700, la de seguridad de OAuth 2.0, resumen lo que una API debe verificar antes de confiar en un token:

ComprobaciónQué significa en la práctica
AlgoritmoLista cerrada de algoritmos permitidos, fijada en el servidor; cada clave se usa con un solo algoritmo
FirmaVerificada siempre con la clave correcta, obtenida de la configuración del emisor de confianza, no de la cabecera del token
Caducidad y validezexp y nbf comprobados, con un margen de reloj pequeño
Emisoriss coincide exactamente con el servidor de autorización esperado
Audienciaaud incluye esta API; un token emitido para otro servicio se rechaza
TipoEl tipo explícito del token (typ) impide aceptar, por ejemplo, un ID token donde se espera un token de acceso
PermisosLos ámbitos (scopes) y roles del token se aplican en cada endpoint, y la autorización por objeto se sigue comprobando aparte
RevocaciónPara operaciones críticas, comprobar que el token o la sesión no se han revocado

La última fila de permisos merece una aclaración: un token válido dice quién llama y qué ámbitos tiene, no si puede acceder al pedido 58213. Esa comprobación por objeto es otro control, y su ausencia es el fallo más frecuente en APIs (BOLA).

En arquitecturas de microservicios, la tentación es validar el token solo en el gateway. Funciona mientras nadie llegue a los servicios internos por otro camino; si es posible, cada servicio debería validar al menos la firma y la audiencia de lo que recibe.

Caducidad, revocación y refresh tokens

El atractivo de un JWT es que la API lo valida sin consultar a nadie. Su contrapartida es que, una vez emitido, sigue siendo válido hasta que caduca, aunque el usuario cierre sesión, cambie la contraseña o se le retire el acceso. La forma habitual de equilibrarlo:

  • Tokens de acceso de vida corta: minutos, no días. Si se roban, el margen de uso es pequeño.
  • Refresh tokens para renovar la sesión, guardados con más cuidado que el token de acceso y revocables en el servidor. RFC 9700 exige que los refresh tokens de clientes públicos —aplicaciones móviles o de navegador, que no pueden guardar un secreto— estén ligados al emisor o se roten en cada uso, de modo que la reutilización de uno ya usado delate el robo.
  • Revocación para los eventos críticos: cambio de contraseña, baja de un empleado o detección de compromiso. Se puede mantener una lista de identificadores de token (jti) o de sesiones revocadas que las APIs consultan, al menos en las operaciones sensibles.
  • Tokens ligados al emisor (sender-constrained): con mTLS (RFC 8705) o DPoP (RFC 9449), el token solo es utilizable por quien posee una clave concreta, así que robarlo no basta. RFC 9700 recomienda usar estos mecanismos.

Un error frecuente en pentests es el cierre de sesión que solo borra el token del navegador: el token sigue funcionando contra la API hasta que caduca, y si caduca en 30 días, el cierre de sesión es decorativo.

Dónde guardar los tokens en una aplicación web

La pregunta «¿localStorage o cookie?» tiene hoy una respuesta oficial más completa. En agosto de 2026 el IETF publicó RFC 10017, «OAuth 2.0 for Browser-Based Applications», que ordena los patrones de arquitectura de más a menos seguro:

PatrónDónde vive el tokenRiesgo principalCuándo encaja
Backend for Frontend (BFF)En un backend propio; el navegador solo tiene una cookie de sesión HttpOnlyHay que proteger la cookie (SameSite, protección CSRF)El RFC lo recomienda expresamente para aplicaciones de empresa, sensibles o que manejan datos personales
Backend mediador de tokensEl backend obtiene los tokens y guarda el refresh token; el navegador recibe el token de acceso y llama con él a las APIsUn XSS puede robar o usar el token de acceso, aunque no el refresh tokenCuando el frontend necesita llamar directamente a APIs de terceros
Cliente OAuth solo en el navegadorEn el JavaScript de la aplicación, en memoria o en almacenamiento del navegadorUn XSS puede robarlos o usarlosEl RFC no lo recomienda para aplicaciones de empresa, sensibles o con datos personales

El motivo es simple: cualquier token al que pueda acceder el JavaScript de la página está al alcance de un atacante que consiga ejecutar código en ella mediante un XSS o una dependencia comprometida. Guardarlo en memoria en lugar de en localStorage reduce la ventana, pero no la cierra. Con un BFF, el navegador nunca ve el token de acceso.

En aplicaciones móviles el equivalente es el almacenamiento seguro del sistema (Keychain en iOS, Keystore en Android) y el flujo de autorización con PKCE a través del navegador del sistema, nunca con una vista web incrustada que pide la contraseña.

Los errores de OAuth 2.0 que prohíben las buenas prácticas actuales

RFC 9700, publicado en enero de 2025, actualiza las recomendaciones de seguridad de OAuth 2.0 con lo aprendido en una década de ataques. Estos son los errores que más se encuentran, con lo que dice el documento:

ErrorQué permiteQué dice RFC 9700
Validación laxa de redirect_uri (prefijos, comodines, subdominios)Que el código de autorización o el token se envíen a una URL del atacanteLos servidores de autorización deben comparar la URI de redirección exactamente, carácter a carácter (salvo el puerto en apps nativas en localhost)
Sin PKCE ni stateInyección de códigos robados y CSRF en el flujoPKCE obligatorio para clientes públicos y recomendado para confidenciales; el servidor debe soportarlo
Flujo implícitoEl token viaja en la URL, expuesto en el historial, logs y cabeceras Referer, y no se puede ligar al clienteLos clientes no deberían usarlo; la alternativa es el flujo de código de autorización con PKCE
Resource owner password credentialsLa aplicación recibe la contraseña del usuario, y el flujo no admite MFA ni federaciónNo debe usarse
Tokens sin audiencia restringidaUn token emitido para una API sirve para otraLos tokens de acceso deberían limitarse a un servidor de recursos concreto
Tokens robados reutilizablesQuien copia un token lo usa desde cualquier lugarSe deberían usar tokens ligados al emisor (mTLS o DPoP)

A esos se suman errores de implementación que no dependen del estándar: secretos de cliente incrustados en apps móviles o en el JavaScript, que dejan de ser secretos en cuanto alguien descarga la aplicación; ámbitos más amplios de lo necesario solicitados «por si acaso»; redirecciones abiertas en el propio cliente que, combinadas con una validación laxa, permiten robar códigos; y tokens que acaban en logs porque se pasan como parámetro de la URL.

OAuth 2.1, que consolida estas prácticas en un solo documento —PKCE para todos, sin flujo implícito ni de contraseña—, sigue siendo un borrador del IETF en octubre de 2026, pero lo que recoge ya está en RFC 9700.

Claves de API, JWT propios u OAuth: qué usar en cada caso

No todas las APIs necesitan OAuth, y forzarlo donde no encaja también genera errores:

EscenarioOpción razonableCuidado con
Aplicación web propia con su backendSesión de servidor con cookie HttpOnly, o BFF si hay un proveedor de identidadMontar un sistema de JWT a mano para algo que una sesión resuelve
App móvil o SPA con usuariosOAuth 2.0 con código de autorización y PKCE contra un proveedor de identidadGuardar secretos de cliente en la app, tokens de larga duración
Aplicaciones de terceros que actúan en nombre de tus usuariosOAuth 2.0 con consentimiento y ámbitos acotadosÁmbitos demasiado amplios, falta de revocación por el usuario
Integración servidor a servidor con un partnerClient credentials de OAuth o claves de API con rotación, limitadas por IP o mTLSClaves compartidas por correo y sin caducidad
Servicios internos entre síIdentidad de servicio (mTLS o tokens de corta duración con audiencia)Confiar en la red interna sin autenticación

Las claves de API no son inseguras por definición, pero identifican a una aplicación, no a un usuario, y suelen vivir años sin rotar. Si se usan, deben poder revocarse una a una, tener el mínimo privilegio y no aparecer nunca en código que se distribuye a clientes.

Cómo se audita la autenticación de una API

En un pentest de APIs, la autenticación con tokens se prueba de forma sistemática, porque un fallo aquí afecta a todos los endpoints a la vez:

  • Manipulación del token: cambiar claims (rol, organización, usuario) y comprobar si la API detecta que la firma ya no cuadra; probar «none», la confusión de algoritmos y tokens sin firma.
  • Secreto HMAC: si el algoritmo es HS256, intentar romper el secreto sin conexión a partir de un token válido.
  • Validación de claims: tokens caducados, con audiencia de otro servicio, de otro emisor o de un entorno de pruebas aceptados en producción.
  • Cabeceras de clave: kid, jku y x5u manipulados.
  • Ciclo de vida: si el cierre de sesión invalida el token en la API, si un refresh token rotado se puede reutilizar, si un cambio de contraseña revoca las sesiones.
  • Flujos OAuth: variaciones de redirect_uri, ausencia de PKCE y state, flujos implícito o de contraseña habilitados, ámbitos que se pueden ampliar en la petición, tokens en URLs y logs.
  • Almacenamiento en el cliente: dónde guarda la aplicación web o móvil los tokens y si un XSS o un análisis de la app los expone.

A esto se suma, siempre que es posible, revisar la configuración de la librería que valida los tokens: muchos de los fallos anteriores no se ven desde fuera hasta que se prueba el caso concreto, y una línea de configuración los explica todos.

FAQ

¿Un JWT está cifrado?

Normalmente no. La mayoría de los JWT están firmados, no cifrados: la cabecera y la carga útil son JSON en base64url y cualquiera puede leerlas. La firma impide modificarlos sin que se note, siempre que se verifique. Si el contenido debe ser confidencial, no debe ir en el token o hay que usar la variante cifrada (JWE).

¿Es mejor usar JWT o sesiones con cookie?

Depende de la arquitectura. Para una aplicación web con su propio backend, una sesión de servidor con cookie HttpOnly es más sencilla y se revoca al instante. Los JWT encajan cuando varias APIs o servicios tienen que validar la identidad sin consultar un almacén central, a cambio de gestionar bien la caducidad y la revocación.

¿Cuánto debería durar un token de acceso?

Lo menos que permita la experiencia de usuario: minutos, no días. La sesión larga se consigue con refresh tokens, que se pueden revocar en el servidor y, en clientes públicos, rotar en cada uso. Un token de acceso que dura semanas convierte cualquier robo en un acceso duradero y deja el cierre de sesión sin efecto real.

¿Puedo usar OAuth 2.0 para el login de mis usuarios?

No directamente: OAuth 2.0 es un marco de autorización y un token de acceso no está pensado para decir quién es el usuario. Para autenticar se usa OpenID Connect, que añade el ID token con la identidad del usuario y cómo se autenticó. Usar un token de acceso como prueba de identidad es un error clásico.

¿Sigue siendo válido el flujo implícito de OAuth?

Está desaconsejado. RFC 9700, la guía de seguridad vigente de OAuth 2.0, establece que los clientes no deberían usarlo, porque expone el token en la URL y no permite ligarlo al cliente. La alternativa es el flujo de código de autorización con PKCE, también en aplicaciones de navegador.

¿Qué es OAuth 2.1 y tengo que migrar?

Es una revisión de OAuth 2.0 que reúne en un solo documento las buenas prácticas ya publicadas: PKCE para todos los clientes, sin flujo implícito ni de contraseña y comparación exacta de las URI de redirección. En octubre de 2026 sigue siendo un borrador del IETF. Si ya sigues RFC 9700, estás prácticamente alineado.

Servicio relacionado

pentesting de APIs, incluidos los flujos OAuth 2.0 y OpenID Connect

Contenido relacionado

Fuentes

Auditar la autenticación y los tokens de mi API