Implementar OAuth 2.0 de forma segura en 2026
La revisión habitual de OAuth termina cuando el inicio de sesión por la ruta esperada funciona. Y ese es el punto de detención equivocado. Los fallos importantes llegan con un callback falsificado, un issuer incorrecto o un token aceptado por la API equivocada.
RFC 9700, publicada como BCP 240 en enero de 2025, actualiza el modelo de amenazas de OAuth con experiencia práctica y amenazas más recientes. Actualiza las RFC 6749, 6750 y 6819. OAuth 2.1 sigue en desarrollo y se espera que incorpore estas recomendaciones.
La draft-ietf-oauth-security-topics-update-02 del 24 de junio de 2026 amplía ese trabajo a las amenazas identificadas desde RFC 9700. Y pertenece a la revisión del backlog, no a tu lista de requisitos RFC MUST actuales.
Tus tickets de brechas de autenticación deberían comenzar con redirecciones exactas, PKCE, vinculación CSRF del callback, defensa contra issuer incorrecto y mix-up, almacenamiento protegido y comprobaciones de audiencia. Y trata DPoP, mTLS y PAR como tickets basados en el riesgo. Por eso esta guía elige primero el flujo, refuerza después el callback y los tokens, y finalmente te proporciona un plan de migración y pruebas.
En este artículo
- OAuth está “funcionando” mucho antes de ser seguro
- Empieza por el flujo, no por la biblioteca
- El flujo en la red debe vincular el código al cliente
- Las URI de redirección son una allowlist, no un patrón
- Trata el callback como un límite de seguridad no confiable
- Mantén los códigos y tokens fuera de lugares que los recuerden o repitan
- Decide cuándo los bearer tokens no son suficientes
- Solicita menos acceso y sobrevive a un “no”
- Añade PAR donde la propia solicitud de autorización merezca protección
- Migra la brecha de autenticación sin interrumpir producción
- Prueba las rutas negativas y el botón de inicio de sesión
OAuth está “funcionando” mucho antes de ser seguro
Un inicio de sesión exitoso solo demuestra que una ruta funciona. Dice poco sobre la inyección de authorization-code, el abuso de redirecciones, la confusión de issuer, la repetición de tokens o las filtraciones a través del comportamiento del navegador y del proxy.
El estándar ahora refleja ese problema más amplio. Pero seguirlo puede romper la interoperabilidad con ecosistemas antiguos. Un despliegue activo necesita una aplicación gradual en lugar de una reescritura de un día para otro. La documentación del proveedor explica los mecanismos de integración; RFC 9700 establece la base de seguridad.
El estándar respalda un conjunto fijo de controles, pero la configuración de OAuth por sí sola no puede indicar si tu riesgo de repetición de tokens justifica DPoP. Mide los datos expuestos, la custodia de claves y la capacidad de respuesta ante incidentes antes de tomar esa decisión.
Rechaza nuevas integraciones que utilicen Implicit o Password grant, URI de redirección con comodines, URL de callback arbitrarias o un intercambio de tokens que permita omitir PKCE. Y la ruta esperada ya tuvo su oportunidad.
Empieza por el flujo, no por la biblioteca
Para usuarios interactivos, elige Authorization Code con PKCE. Los clientes públicos MUST usar PKCE; se RECOMMENDED que los clientes confidenciales lo utilicen. “Confidential” describe dónde puede conservarse un client secret. Los clientes confidenciales también se benefician de PKCE.
Un client secret dentro de un binario móvil es decoración, no autenticación.
| Cliente/carga de trabajo | Usar | Protecciones mínimas | No usar |
|---|---|---|---|
| Aplicación web del lado del servidor | Authorization Code + PKCE | URI de redirección exacta, state, comprobaciones de issuer, almacenamiento protegido de tokens en el servidor | Implicit o Password grant |
| Aplicación nativa o cliente público | Authorization Code + PKCE | Aplicación obligatoria de PKCE, reglas de redirección exactas, state, almacenamiento de tokens de la plataforma | Embedded webviews o client secrets tratados como prueba |
| Aplicación de navegador | Authorization Code + PKCE | PKCE, protección del callback, exposición limitada de tokens | Tokens en URL o almacenamiento legible casualmente por el navegador |
| Servicio machine-to-machine | Client Credentials | Autenticación del cliente, tokens restringidos por audiencia, credenciales protegidas | Concesiones de usuario interactivas |
| Integración heredada existente | Migrar hacia la fila aplicable | Telemetría, aplicación gradual, revisión de compatibilidad | Otra excepción permanente |
Para una integración de calendario SaaS multi-tenant, la aplicación web es un cliente interactivo de authorization-code tanto si se conecta con Google como con Microsoft. Revisa su API de alto valor por separado. Un proceso backend programado que llama a una API sin un usuario pertenece a la fila Client Credentials.
Para los tickets machine-to-machine, especifica el método de autenticación que admite tu authorization server, exige un token restringido por audiencia y almacena la credencial de forma segura. Client Credentials identifica la concesión; no resuelve por sí mismo la autenticación del servicio.
La documentación de Google para servidores web y la documentación de Microsoft sobre authorization-code cubren los mecanismos específicos del proveedor. Usa esos detalles sin permitir que la conveniencia del proveedor prevalezca sobre la base de seguridad.
El flujo en la red debe vincular el código al cliente
Crea un code_verifier nuevo y de alta entropía para cada solicitud de autorización. Deriva su challenge con SHA-256 y codificación base64url:
code_challenge = BASE64URL(SHA256(code_verifier))
Para la conexión con el calendario, la secuencia de solicitudes tiene este aspecto:
# Authorization request
GET /authorize?
response_type=code&
client_id=calendar-web&
redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback%2Fgoogle&
scope=calendar.read&
state=<random-state>&
code_challenge=<base64url-sha256-verifier>&
code_challenge_method=S256
# Token exchange over the back channel
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
client_id=calendar-web&
redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback%2Fgoogle&
code=<authorization-code>&
code_verifier=<original-verifier>
El navegador transporta la solicitud de autorización y el código resultante de corta duración. Tu backend intercambia ese código mediante el back channel. Si el servidor exige redirect_uri en ambas etapas, envía el mismo valor registrado. Rechaza las discrepancias.
Cuando la solicitud de autorización incluye un challenge PKCE, el authorization server MUST rechazar una solicitud de token sin el verifier correspondiente. Esto cierra una degradación en la que un intermediario elimina PKCE del intercambio.
Conserva el verifier junto a la transacción pendiente del lado del servidor. Elimínalo después de un intercambio exitoso o fallido. Nunca aceptes un verifier de una sesión de navegador no relacionada.
La guía de Google sobre PKCE y DPoP explica cómo los dos controles cubren partes diferentes del flujo.
Las URI de redirección son una allowlist, no un patrón
Registra URI de redirección completas y compara el valor completo. RFC 9700 dice:
“Authorization servers MUST compare client redirection URIs exactly, except for port numbers in localhost redirection URIs of native apps.”
Por ejemplo, si registras:
https://app.example.com/oauth/callback/google
el servidor debe rechazar:
https://app.example.com/oauth/callback/google/tenant-admin
Una URI de redirección con comodín es una allowlist que alguien olvidó terminar. La coincidencia flexible de prefijos y rutas puede enviar un código a un endpoint no previsto. PKCE vincula el intercambio a un verifier; la validación de redirecciones sigue siendo un control independiente.
Define explícitamente la regla de comparación y prueba cada diferencia de esquema, host, puerto, ruta y query. Aplica la excepción de puerto localhost únicamente a las redirecciones localhost de aplicaciones nativas. Nunca aceptes una URL de callback proporcionada dinámicamente.
Un open redirect cerca de la ruta del callback crea un relay para respuestas sensibles. Un endpoint que acepta ?next= y reenvía a cualquier lugar no tiene cabida en la ruta de la transacción.
La investigación revisada por pares sobre la validación de URI de redirección encontró debilidades entre la política prevista y la validación desplegada. Usa esa advertencia para probar tu propia implementación.
- Compara la cadena registrada exacta. El esquema, host, puerto, ruta y query importan.
- Rechaza comodines y prefijos. “Empieza por nuestro dominio” no es una regla de seguridad.
- Selecciona entre valores registrados. No permitas que los parámetros de la solicitud creen callbacks.
- Elimina los open redirects. Un callback debe completar la transacción y mantener al usuario en el flujo de la transacción.
Trata el callback como un límite de seguridad no confiable
Tu callback recibe entradas controladas por un atacante. Trátalo como un endpoint de API que casualmente llega a través de un navegador.
¿Es suficiente PKCE?
Solo de forma condicional. PKCE puede sustituir a la protección CSRF basada en state cuando hayas confirmado que el authorization server admite PKCE. En un despliegue con múltiples proveedores, state sigue siendo el valor predeterminado más seguro porque vincula la respuesta a la transacción del navegador que la inició.
Genera un state único y no predecible. Vincúlalo a la sesión del usuario, compáralo exactamente y consúmelo una sola vez. La guía de Auth0 sobre state describe este propósito de vinculación de transacciones.
pending = load_transaction(returned_state)
if pending is missing or expired:
reject
if pending.session_id != current_session.id:
reject and consume transaction
if returned_state != pending.state:
reject and consume transaction
if returned_issuer != pending.expected_issuer:
reject and consume transaction
exchange code using:
pending.client_id
pending.registered_redirect_uri
pending.code_verifier
validate issuer and audience wherever the received token format exposes
and supports those claims; otherwise use the provider's documented
introspection or validation mechanism
map identity by (issuer, subject)
consume transaction
create application session
Admitir Google y Microsoft crea un problema obligatorio de defensa contra mix-up. Usa el parámetro iss definido por RFC 9207 o URI de redirección distintas para cada authorization server:
/oauth/callback/google
/oauth/callback/microsoft
Conserva la identidad como (issuer, subject). Un valor subject sin su issuer es ambiguo.
Para OIDC, verifica nonce en el ID token como comprobación adicional contra la repetición y de vinculación de la respuesta; conserva las protecciones del callback exigidas por tu flujo. state, PKCE, las comprobaciones de issuer y nonce cumplen funciones distintas.
Mantén los códigos y tokens fuera de lugares que los recuerden o repitan
Los authorization codes pueden filtrarse a través de canales visibles para el navegador; los access y refresh tokens introducen riesgo de repetición después de su emisión. RFC 9700 identifica los encabezados referrer, el historial del navegador, las filtraciones del resource server y el reenvío accidental de credenciales mediante redirecciones HTTP 307.
Mantén las credenciales fuera de las query strings y, en general, de las URL. Establece una Referrer-Policy adecuada. Limpia con rapidez el estado sensible del callback. Inspecciona el comportamiento del reverse proxy en torno a las redirecciones.
| Entorno | Enfoque de almacenamiento | Límite principal |
|---|---|---|
| Aplicación web del lado del servidor | Almacén protegido del lado del servidor; credenciales del cliente en un secret manager | Mantener los tokens fuera de las respuestas y logs del navegador |
| Aplicación móvil nativa | Android Keystore, iOS Keychain o Windows Credential Locker | Usa un navegador completo o una biblioteca OAuth de la plataforma, no Android WebView ni iOS WKWebView |
| Aplicación de navegador | Preferiblemente un backend-for-frontend y una sesión del servidor | Si los tokens permanecen en el navegador, documenta la exposición a XSS y usa el patrón authorization-code compatible con el proveedor |
Evita poner refresh o access tokens en localStorage: JavaScript ejecutado mediante una vulnerabilidad XSS puede leerlos. Para un conector del lado del servidor, almacena los refresh tokens en un almacén protegido, restringe el acceso al descifrado cuando se utilice cifrado, redacta los tokens de los logs y revócalos y elimínalos al desconectar.
Para los refresh tokens, implementa rotación cuando sea compatible, detecta la reutilización, revócalos ante una sospecha de compromiso y elimínalos cuando ya no sean necesarios.
Desactiva las redirecciones desde las solicitudes al endpoint de tokens cuando sea posible. De lo contrario, asegúrate de que el cliente HTTP no pueda reenviar solicitudes con credenciales a otro origen.
Decide cuándo los bearer tokens no son suficientes
Un bearer access token funciona para quien lo presente. La restricción de audiencia reduce dónde se acepta un token. La sender constraint exige poseer una clave de cliente o un certificado asociado.
DPoP vincula un token a un par de claves controlado por el cliente. Solo funciona cuando el authorization server emite tokens restringidos al remitente y el resource server verifica las pruebas. Esto implica generar claves y proteger su almacenamiento. También necesitas pruebas de solicitudes firmadas, gestión del reloj, detección de repeticiones, rotación y respuesta ante incidentes.
Usa esta regla:
- API interna ordinaria y de bajo impacto: implementa primero la base y la restricción de audiencia.
- Cliente público o API de alto valor: considera firmemente DPoP.
- Entorno regulado o vinculado a infraestructura: evalúa DPoP frente a mTLS y a las restricciones de tu despliegue.
Para la API de alto valor de la aplicación SaaS, DPoP puede justificar su coste operativo si la repetición de bearer tokens pudiera exponer datos sensibles de los tenants. Conserva la clave DPoP en el almacén de claves protegido del backend. Una clave conservada en el navegador tiene un modelo de compromiso diferente y merece una revisión independiente.
PAR y sender constraint son componentes útiles de infraestructura de seguridad, pero desplegar cualquiera de ellos en todas partes convertiría un control condicional en simple ceremonia.
Solicita menos acceso y sobrevive a un “no”
Solicitar todos los permisos durante el primer inicio de sesión proporciona al cliente un acceso que quizá no necesite y aumenta la fricción para el usuario. Solicita scopes cuando el usuario llegue a la función que los necesita.
- Asigna a cada función los scopes que requiere.
- Solicita scopes básicos durante la conexión inicial.
- Solicita un scope elevado cuando el usuario active la función correspondiente.
- Si el usuario lo rechaza, desactiva esa función y mantén utilizable el resto.
- Explica el motivo antes de volver a solicitarlo.
- Elimina clientes y credenciales que no utilices.
Una función de lectura del calendario puede seguir disponible cuando se rechaza el permiso de escritura del calendario. No conviertas un scope rechazado en un inicio de sesión fallido para toda la aplicación.
Google dice que elimina automáticamente los clientes OAuth inactivos durante seis meses. Esa es una indicación operativa específica de Google, así que elimina proactivamente los clientes que no utilices en lugar de depender de la política.
Añade PAR donde la propia solicitud de autorización merezca protección
Las Pushed Authorization Requests trasladan los parámetros de autorización mediante un back channel antes de que el navegador llegue al endpoint de autorización. Después, el navegador transporta una referencia breve a la solicitud en lugar de toda la carga de autorización.
Usa PAR cuando tu authorization server y tu cliente lo admitan, y verifica que la solicitud de autorización utilizada en el front channel coincida con la solicitud enviada. oauth.net identifica PAR como preferible para despliegues de alta seguridad.
Trata PAR como un control específico de la carga de trabajo. Merece su lugar cuando la integridad de la solicitud, la exposición de parámetros o los requisitos de garantía justifiquen otro endpoint y otra ruta operativa.
Migra la brecha de autenticación sin interrumpir producción
Haz primero un inventario, porque la aplicación de controles expondrá clientes que dependen de comportamientos obsoletos. Registra cada cliente, authorization server, URI de redirección, grant y scope. Registra también la duración del token, la ubicación del almacenamiento y la audiencia del resource server. Después clasifica los clientes como públicos, confidenciales, nativos, de navegador o machine-to-machine.
Aplica la base ahora
Rechaza nuevas integraciones Implicit y Password. Para los clientes existentes, introduce registro exacto de redirecciones, PKCE, aplicación del verifier, state, validación de issuer y defensas contra mix-up. Traslada los secretos y tokens a almacenamiento protegido y añade la restricción de audiencia.
Asigna a cada cambio un responsable, una métrica de fallos, un interruptor de rollback y una fecha de retirada. Aplica primero los cambios a los clientes nuevos y después migra los clientes existentes por cohortes.
Refuerzo condicional después
Evalúa DPoP o mTLS para tokens restringidos al remitente. Añade PAR donde la solicitud de autorización justifique protección. Estos controles dependen de la carga de trabajo, el soporte del proveedor, la custodia de claves y la capacidad del resource server para verificar lo que recibe.
Evidencia antes de aplicar los controles
Introduce cada regla gradualmente con telemetría para los fallos heredados. Un cliente que falla después de aplicar una redirección exacta necesita un responsable y una ruta de migración. Las excepciones silenciosas se convierten en deuda. La compatibilidad que exige aceptar un ataque es deuda con un presupuesto de disponibilidad.
El borrador -02 de junio de 2026 pertenece a esta revisión del backlog. Es un borrador de IETF, no una RFC final. Úsalo para preparar pruebas y futuros tickets, no para presentar sus propuestas como requisitos obligatorios actuales.
Prueba las rutas negativas y el botón de inicio de sesión
Construye la revisión a partir de los pares ataque-mitigación del estándar. Prueba:
- URI de redirección alterada o no registrada;
- coincidencia mediante prefijos, comodines y confusión de rutas;
- open redirect en el callback;
stateausente, discrepante, reutilizado o predecible;- issuer incorrecto o ausente;
- intercambio de authorization-code sin verifier;
- verifier incorrecto y reutilización del verifier;
- authorization code intercambiado;
- token con la audiencia incorrecta;
- tokens y códigos presentes en query strings;
- respuestas del callback sin una
Referrer-Policyadecuada; - repetición y detección de reutilización del refresh token;
- redirecciones del endpoint de tokens que reenvían credenciales mediante un 307;
- mix-up entre múltiples authorization servers;
- omisión de PKCE en clientes públicos.
Para la prueba de redirección, espera un rechazo antes de la entrega del código. La prueba del callback debe fallar antes de crear la sesión. Las pruebas de tokens deben hacer que el servidor o resource server rechace el valor y registre el evento sin escribir la credencial en los logs.
Revisa la actualización del borrador de IETF del 24 de junio de 2026 para conocer las amenazas emergentes. Sigue siendo un borrador.
Antes del despliegue, exige fallos registrados para una redirección no registrada, un verifier ausente o incorrecto, un state discrepante, un issuer o audiencia incorrectos y un refresh token repetido. Bloquea el lanzamiento hasta que esas cinco pruebas fallen en CI o en un entorno equivalente de pruebas de seguridad.