OAuth2 es sencillo en el pizarrón y un problema real en producción

Este artículo sostiene que OAuth2 es engañosamente simple en el pizarrón y falla seguido en producción, y detalla dónde se equivocan realmente los equipos senior: almacenamiento inseguro de tokens, mala rotación y revocación de refresh tokens, scopes demasiado amplios, y la falsa seguridad de una librería estándar.

OAuth2 es sencillo en el pizarrón y un problema real en producción featured image
17 ago 20269 min de lectura
Actualizado el 24 ago 2026

Ya implementaste "iniciar sesión con Google" más veces de las que puedes contar. Dibujaste el flujo de authorization code de OAuth2 en el pizarrón en más de una entrevista técnica, con las flechas del redirect y el candado imaginario sobre el token. Sabes qué es un scope. Leíste el RFC completo, o al menos lo hojeaste una tarde tranquila entre deploys. Por cualquier criterio razonable, entiendes OAuth2.

Después lo llevas a producción, y seis meses más tarde llega un informe de pentest con hallazgos que te dan ganas de desaparecer detrás del teclado.

Esa distancia no existe porque OAuth2 sea complicado. El spec es casi insultantemente simple una vez que ves el diagrama: redirect, consentimiento, code, intercambio por token, listo. La distancia existe porque OAuth2 es un framework, no una receta. Deja decenas de decisiones en manos de quien lo implementa, y la mayoría de los equipos las toma una sola vez, bajo presión de entrega, y nunca las vuelve a revisar. El resultado son sistemas en producción que pasan todos los code reviews y aun así filtran tokens, confunden quién es el usuario con qué puede hacer ese usuario, y confían en scopes que nadie audita hace dos años.

Esto es lo que pasa en la práctica, y por qué "usamos una librería estándar" no es la coartada que la mayoría de los ingenieros cree que es.

OAuth2 nunca fue diseñado para responder "quién es este usuario"

Esta es la confusión que más daño causa río abajo, porque al principio parece inofensiva. OAuth2 es un protocolo de autorización. Un intercambio de token exitoso demuestra que una aplicación tiene permiso para llamar a una API en nombre de alguien. No demuestra quién es esa persona. En algún punto, una cantidad enorme de equipos empezó a tratar "el usuario completó el flujo de OAuth2" como sinónimo de "el usuario está autenticado", y construyó toda su lógica de sesión sobre esa suposición.

Funciona bien hasta que deja de funcionar. El access token que recibes puede ser opaco o, si es un JWT, puede traer un claim `sub` que alguien del equipo decide que alcanza para identificar al usuario. Nadie valida la audiencia del token. Nadie verifica que haya sido emitido realmente para tu aplicación y no para otro cliente que comparte el mismo authorization server. OpenID Connect existe como una capa sobre OAuth2 justamente para cerrar esta brecha, con un ID token propio y claims de identidad en los que sí puedes confiar. Los equipos que se lo saltan e improvisan su propia lógica de "identidad a partir del access token" son los que terminan con bugs de account takeover que nunca aparecen en una demo, solo en el post mortem de un incidente.

Dónde mueren realmente los access tokens

El almacenamiento de tokens es la parte a la que todo equipo llega tarde o temprano, y casi ninguno resuelve bien la primera vez. Apps web que guardan el access token en `localStorage` porque es el camino de menor resistencia durante el sprint, sin registrar que cualquier XSS exitoso en esa página ahora tiene una vía directa para robar una sesión activa. Apps móviles que dejan tokens en shared preferences o en un plist sin ninguna capa de cifrado, porque la librería de OAuth2 resolvió el flujo y nadie pensó qué pasaba después de que el token quedaba guardado en el disco. Middleware de logging que vuelca headers completos, Authorization incluido, a un agregador con seis meses de retención que nadie recuerda que existe hasta que una revisión de seguridad lo detecta.

Hay también una versión de este error que parece más sofisticada y no lo es. Equipos que almacenan el token correctamente en el cliente, pero después lo reenvían tal cual a cada microservicio interno como mecanismo de autenticación de facto. Un solo servicio comprometido ahora tiene en sus manos un token con el alcance de toda la sesión del usuario, no solo el pedazo de trabajo que le correspondía. El almacenamiento de tokens no es solo un problema del cliente. Es un problema arquitectónico, y no deja de importar cuando el token sale del navegador o de la app.

Ninguno de estos arreglos es exótico. Tokens de vida corta, cookies httpOnly donde la arquitectura lo permite, almacenamiento seguro nativo de la plataforma en mobile, y una limpieza de logs que saca los headers de autenticación antes de persistir nada. El arreglo es aburrido. Justamente por eso se salta: no aparece en ninguna demo, y no es la parte de OAuth2 que nadie pone en una slide.

La rotación de refresh tokens es lo que ningún equipo termina de construir

Todo el mundo implementa el intercambio de access token, porque sin eso no hay forma de lanzar nada. Casi nadie termina de implementar bien la rotación de refresh tokens, porque sí se puede lanzar sin eso y los atajos no se notan hasta que algo sale mal. Rotación significa que cada vez que se usa un refresh token, el anterior se invalida y se emite uno nuevo. Si ese token invalidado vuelve a aparecer en algún momento, es una señal de que alguien más tiene una copia, y toda la familia de tokens debería revocarse de inmediato.

La mayoría de las implementaciones se saltan esto. Un refresh token vive treinta o noventa días, se reutiliza sin ningún seguimiento, y si se filtra, la exfiltración es invisible hasta que alguien nota actividad rara semanas después, si es que la nota. Incluso los equipos que sí implementan rotación muchas veces se saltan la detección de reutilización, lo cual anula el sentido de rotar en primer lugar. Y la revocación, la parte donde cerrar sesión, cambiar la contraseña o que un admin marque una cuenta comprometida realmente mata los tokens correspondientes del lado del servidor, se trata como un "nice to have" hasta que un incidente lo vuelve urgente. Para entonces es un hotfix bajo presión en vez de una decisión de diseño tomada con calma de antemano.

El scope que agregaste "por ahora" hace dos años sigue ahí

Los scopes existen para limitar el radio de impacto. En la práctica, los equipos piden scopes amplios desde el principio porque la integración funciona así, se dicen a sí mismos que después lo van a acotar, y ese después nunca llega. Dos años más tarde, una integración de terceros todavía tiene acceso de lectura y escritura a todo el calendario del usuario, porque acotarlo a "leer eventos de este calendario en particular" hubiera significado trabajo extra en un sprint que ya tenía demasiado encima.

Después las credenciales de esa integración se filtran, o el tercero sufre una brecha, y el radio de impacto termina siendo todo el calendario en vez del pedazo mínimo de datos que la integración realmente necesitaba. El principio de mínimo privilegio no es un checkbox de compliance que se marca para pasar una auditoría. Es el único factor que decide si una brecha termina siendo un incidente contenido o un desastre completo, y no cuesta nada hacerlo bien salvo la disciplina de decir que no al scope más amplio cuando el más chico alcanza.

Una librería estándar de OAuth2 no te va a salvar de ti mismo

Este es el mito que más daño causa, porque suena completamente razonable. "Usamos Auth0, Okta, Passport, lo que sea, así que estamos cubiertos." Una buena librería resuelve bien la mecánica del protocolo. Arma las URLs de redirect correctas, valida firmas, intercambia codes por tokens según el spec, y te salva de una categoría entera de errores que eran comunes cuando cada equipo escribía su propio cliente de OAuth2 a mano.

Lo que no hace es elegir tus scopes por ti. No decide dónde se guarda el token que te entrega. No define la duración de tu sesión, tu política de rotación, ni qué tan estricta necesita ser la validación de tu redirect URI para bloquear un open redirect. Cada decisión de seguridad real en una implementación de OAuth2 vive en la configuración y en el código de la aplicación que envuelve a la librería, que es justamente la parte que la librería no puede ver. Usar una librería de OAuth2 bien mantenida descarta toda una categoría de errores. No descarta los que tu equipo está cometiendo ahora mismo, y tratarla como una garantía de seguridad es exactamente cómo los equipos dejan de buscar los errores que sí les corresponde resolver.

OAuth2 no es la vulnerabilidad, el abandono sí

Nada de esto significa que OAuth2 sea un mal spec, y mucho menos que debas armar algo propio en su lugar. Ese camino es peor, siempre. Lo que significa es que la parte difícil de OAuth2 nunca fue el diagrama de flujo que dibujaste en una entrevista. La parte difícil es el mantenimiento poco glamoroso que casi nadie quiere hacerse cargo: revisar qué scopes realmente siguen siendo necesarios, rotar tokens como el spec lo indica en vez de como fue más rápido lanzarlo, decidir dónde viven los tokens antes de que una auditoría de seguridad te obligue a responder esa pregunta bajo presión.

OAuth2 en producción no es un protocolo que se implementa una vez y se olvida. Es un conjunto de decisiones que hay que seguir tomando bien mientras el sistema siga vivo. La mayoría de los equipos deja de tomarlas justo después del lanzamiento, cuando el diagrama todavía está fresco y la demo todavía funciona. Eso no es una falla del spec. Esa es la vulnerabilidad real, y está en tu código ahora mismo, lo haya revisado alguien o no.

ESCRITO POR

Equipo de redacción de contenido de Howdy
Howdy Editorial Team
COMPARTIR