Seguridad e identidad

OAuth 2.0 explicado: roles, grants, PKCE y buenas prácticas actuales

Intermedio8 min de lecturaActualizado

Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés

OAuth 2.0 permite que una aplicación actúe en nombre de un usuario sin llegar a ver nunca su contraseña, y casi todos los errores con él nacen de no entender bien qué delega. Esta guía recorre los roles, los mensajes HTTP del authorization code flow con PKCE, los demás grants que conviene conocer, cómo funcionan los scopes y el consentimiento, y qué elimina de la especificación original la práctica de seguridad recomendada hoy.

Lo que aprenderás

  • Explicar qué delega OAuth 2.0 y por qué es autorización y no autenticación
  • Seguir el authorization code flow con PKCE mensaje a mensaje
  • Elegir el grant adecuado para una aplicación de navegador, una app móvil, un proceso backend o un televisor
  • Usar correctamente scopes, consentimiento, refresh tokens, introspección y revocación
  • Aplicar las reglas de la RFC 9700 sobre redirect URIs, grants obsoletos y sender-constrained tokens

OAuth 2.0 existe para responder a una pregunta muy concreta: ¿cómo puede una aplicación llamar a una API en tu nombre sin que le entregues tu contraseña? Antes de OAuth, la respuesta sincera era que le dabas tus credenciales a la aplicación y cruzabas los dedos, lo que significaba que cada integración tenía las llaves de toda tu cuenta y no había forma de revocarlas una a una. OAuth lo sustituye por un token —acotado, con caducidad y revocable— que se emite después de que apruebes una solicitud concreta en un sitio en el que ya confías.

El framework es pequeño, pero a menudo se malinterpreta. Es un framework de autorización, no un protocolo de autenticación; deliberadamente, la especificación no dice nada sobre cómo demostró el usuario quién es. Casi todos los problemas serios de OAuth que se ven en producción vienen de olvidar esa distinción o de implementar un flujo que el ecosistema ya ha retirado. Esta guía cubre los roles, los flujos que conviene usar en 2026, cómo se comportan los tokens y las reglas de la RFC 9700 que sustituyen partes de la RFC 6749 original.

Los cuatro roles

  • El resource owner es el usuario al que pertenecen los datos: normalmente una persona, a veces una organización.
  • El cliente es la aplicación que quiere acceder a ellos. Es confidencial si puede guardar un secreto (una aplicación web del lado del servidor) y público si no puede (una app móvil, una aplicación que se ejecuta en el navegador).
  • El authorization server autentica al resource owner, obtiene su consentimiento y emite los tokens. Publica un authorization endpoint (front channel, navegador) y un token endpoint (back channel, HTTPS directo).
  • El resource server es la API que acepta el token y sirve los datos.

Hay dos canales de comunicación que importan en todo momento. El front channel pasa por el navegador del usuario mediante redirecciones: es visible, queda registrado y se puede manipular, así que nada secreto puede viajar por él. El back channel es una llamada HTTPS directa de servidor a servidor, autenticada y privada. En el fondo, diseñar cualquier flujo OAuth consiste en decidir qué valor va por qué canal.

Authorization code flow con PKCE, paso a paso

Es el flujo que debes usar tanto en aplicaciones de navegador como en apps móviles y en aplicaciones web renderizadas en el servidor. Proof Key for Code Exchange (RFC 7636) nació como un parche para móviles; hoy es obligatorio para todos.

Primero, el cliente genera una cadena aleatoria de alta entropía, el code verifier, y deriva de ella un code challenge aplicando un hash SHA-256 y codificándolo en base64url. Por el navegador solo pasa el challenge.

code_verifier  = 43-128 chars of unreserved randomness, e.g. "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
code_challenge = BASE64URL( SHA256( code_verifier ) )

1. Solicitud de autorización. El cliente redirige el navegador al authorization endpoint.

GET /authorize?response_type=code
  &client_id=s6BhdRkqt3
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=orders.read%20orders.write
  &state=af0ifjsldkj
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256 HTTP/1.1
Host: auth.example.com

El parámetro state es un valor opaco que el cliente guarda en su sesión y comprueba a la vuelta; vincula la respuesta con la solicitud y frena los ataques de cross-site request forgery contra el endpoint de redirección.

2. Autenticación y consentimiento. El servidor de autorización autentica al usuario como considere oportuno —contraseña más un segundo factor, una passkey, una sesión ya abierta— y muestra una pantalla de consentimiento con los scopes solicitados. El cliente no participa en nada de esto.

3. Redirección de vuelta con un código.

HTTP/1.1 302 Found
Location: https://app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj

El authorization code es de un solo uso, dura muy poco (de unos segundos a un par de minutos) y por sí solo no sirve para nada.

4. Solicitud de token por el back channel. Ahora el cliente demuestra que fue él quien inició el flujo enviando el verifier original.

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

5. Respuesta con el token.

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDMifQ...",
  "token_type": "Bearer",
  "expires_in": 300,
  "refresh_token": "8xLOxBtZp8",
  "scope": "orders.read orders.write"
}

El servidor de autorización recalcula SHA256(code_verifier) y lo compara con el challenge que guardó. Un atacante que de algún modo haya interceptado el código en el front channel no puede canjearlo, porque el verifier nunca salió del cliente legítimo.

6. Llamada a la API. El access token viaja en la cabecera Authorization, tal como define la RFC 6750, y el resource server lo valida antes de hacer cualquier otra cosa.

GET /orders/1042 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDMifQ...

Cómo debe hacerse esa validación —comprobación de la firma, validación de los claims, rotación de claves— es el tema de proteger APIs REST con JWT. Si necesitas inspeccionar un token mientras depuras, un decodificador de JWT que funciona en el navegador muestra los claims sin enviarlo a ninguna parte.

Los otros grants y los que ya no existen

Client credentials está pensado para llamadas entre máquinas en las que no hay ningún usuario: un proceso nocturno, un servicio que llama a otro. El cliente se autentica y recibe un token que lo representa a él mismo.

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=ledger.post

Aquí no hay refresh token —el cliente simplemente vuelve a pedir uno— y no debe asociarse a ese token ninguna lógica de autorización propia de un usuario, porque no representa a ninguno.

El device authorization grant (RFC 8628) cubre los dispositivos con capacidad de entrada limitada: un televisor, una impresora, una herramienta de línea de comandos. El dispositivo solicita un código, muestra un código de usuario corto y una URL, y consulta periódicamente el token endpoint mientras el usuario completa el flujo en el móvil o el portátil.

POST /device_authorization HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

client_id=tv-app&scope=video.play
{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://auth.example.com/device",
  "expires_in": 1800,
  "interval": 5
}

El refresh token grant cambia un refresh token por un nuevo access token sin interacción del usuario. Los clientes públicos deben aplicar rotación de refresh tokens: cada uso devuelve un refresh token nuevo e invalida el anterior, y si reaparece un token ya usado, el servidor revoca toda la cadena, porque ese patrón indica que se robó una copia.

La RFC 9700 prohíbe ahora dos grants de la especificación original. El implicit grant devolvía el access token directamente en el fragmento de la URL, desde donde se filtraba al historial del navegador, a los referrers y a cualquier script de la página; el code flow con PKCE cubre el mismo caso de uso de forma segura. El resource owner password credentials grant obligaba al cliente a manejar la contraseña del usuario, lo que echa por tierra la razón de ser del framework e impide la autenticación multifactor y el step-up. El sector llama OAuth 2.1 a la consolidación de estas reglas.

Scopes, consentimiento y audiencia

Un scope es una etiqueta de grano grueso para un permiso que solicita el cliente: orders.read, profile, payments.initiate. El servidor de autorización decide qué scopes puede llegar a pedir cada cliente, el usuario da su consentimiento y el resource server los hace cumplir. Tres advertencias:

  • Los scopes describen lo que el cliente puede intentar, no lo que el usuario puede hacer. Un token con orders.read sigue obligando a la API a comprobar que este usuario puede leer este pedido. Saltarse esa comprobación es el fallo de control de acceso relacionado con OAuth más habitual.
  • Mantén pocos scopes y que tengan sentido. Cientos de scopes muy granulares generan pantallas de consentimiento ilegibles, y un consentimiento que nadie lee no es consentimiento.
  • Usa el audience (o un resource indicator) para que un token emitido para una API no pueda reutilizarse contra otra. Un token que se acepta en todas partes convierte el compromiso de un único servicio en un compromiso total.

En los clientes first-party, las organizaciones a menudo se saltan la pantalla de consentimiento; es una cuestión de política que decide el servidor de autorización, no algo que el cliente pueda dar por hecho.

Vida de los tokens, introspección y revocación

Los access tokens deben durar poco —minutos, no días— porque son bearer credentials: quien tenga uno puede usarlo. Los refresh tokens duran mucho y, por eso, hay que almacenarlos con cuidado y vincularlos al cliente.

Los tokens tienen dos formas. Un token autocontenido (normalmente un JWT firmado) puede validarlo sin conexión cualquier resource server que tenga las claves públicas del emisor, lo que escala bien pero implica que la revocación no es instantánea. Un token opaco es un identificador aleatorio que el resource server tiene que consultar, lo que permite revocar al instante a costa de una llamada de red en cada petición; la introspección de la RFC 7662 define esa consulta.

POST /introspect HTTP/1.1
Host: auth.example.com
Authorization: Basic cmVzb3VyY2Utc2VydmVyOnNlY3JldA==
Content-Type: application/x-www-form-urlencoded

token=mF_9.B5f-4.1JqM
{ "active": true, "scope": "orders.read", "client_id": "s6BhdRkqt3", "sub": "u-88231", "exp": 1789012345 }

Un término medio práctico es combinar JWT de vida corta con la revocación del refresh token, aceptando una ventana acotada durante la cual una sesión revocada sigue funcionando. Cuando la revocación instantánea es un requisito estricto —pagos, acceso de administración—, un API gateway puede hacer la introspección de forma centralizada y cachear el resultado unos segundos.

Reglas de seguridad que no son opcionales

  • Registra redirect URIs exactas y compáralas como cadenas, carácter a carácter. Los comodines y la coincidencia por prefijo son la vía por la que los tokens acaban redirigidos a rutas que controla un atacante. Un open redirect en cualquier parte del host registrado reintroduce el mismo problema.
  • Envía siempre PKCE y comprueba siempre state. Defienden de ataques distintos: PKCE vincula el código al cliente y state vincula la respuesta a la sesión.
  • No pongas nunca tokens en la query string. Las URL acaban en los logs de acceso, en los proxies y en las cabeceras Referer.
  • Vincula los tokens a quien los presenta cuando el riesgo lo justifique. La vinculación a un certificado de TLS mutuo (RFC 8705) o DPoP (RFC 9449) hace que un token robado no sirva de nada sin la clave correspondiente.
  • Valida el parámetro iss de la respuesta de autorización cuando el cliente hable con más de un servidor de autorización, para evitar los ataques de mix-up.
  • No uses un access token para identificar al usuario. Usa OpenID Connect y su ID token, que se emite para tu cliente y que tu cliente valida.

En cuanto a la implementación, la mayoría de los frameworks ya hacen lo correcto por defecto: Spring Security envía PKCE tanto para clientes públicos como confidenciales y valida state por ti. Escribe el flujo tú mismo solo si no te queda más remedio y, en ese caso, sigue la especificación al pie de la letra.

Ideas clave

  • OAuth 2.0 delega el acceso a una API sin compartir credenciales; deliberadamente no dice nada sobre cómo se autenticó el usuario.
  • Los valores que pasan por el navegador son públicos; solo el back channel puede transportar secretos, y por eso existe el code flow.
  • Usa el authorization code flow con PKCE en todo cliente interactivo, client credentials en las llamadas entre máquinas y el device grant en los dispositivos con entrada limitada.
  • La RFC 9700 elimina los grants implicit y password; los refresh tokens de los clientes públicos deben rotar y revocarse en cadena si se reutilizan.
  • Los scopes limitan lo que un cliente puede intentar; la API sigue teniendo que autorizar al usuario frente al recurso concreto.
  • La coincidencia exacta de redirect URIs, state, los access tokens de vida corta y la restricción de audiencia convierten una integración que funciona en una integración segura.

Fuentes y lecturas recomendadas

Preguntas frecuentes

¿OAuth 2.0 es un protocolo de inicio de sesión?
No. Es un framework de autorización delegada: produce un access token que indica que un cliente puede llamar a una API con ciertos scopes, y no dice nada estándar sobre quién es el usuario ni cuándo se autenticó. Usar un access token como prueba de inicio de sesión es un antipatrón conocido, porque ese token se emitió para una API, no para tu aplicación. OpenID Connect añade la capa de identidad que responde a esas preguntas.
¿Los clientes confidenciales siguen necesitando PKCE?
Sí. La RFC 9700 exige PKCE en todo intercambio de authorization code, no solo en los clientes públicos. Un client secret demuestra qué aplicación canjea el código, pero PKCE además vincula ese código a la sesión de navegador concreta que inició el flujo, y eso es lo que frena los ataques de inyección e interceptación de código en entornos de redirección compartidos.
¿Dónde debe guardar sus tokens una single-page application?
Idealmente, en ningún sitio. El patrón recomendado es un backend-for-frontend que realiza el intercambio del código en el servidor y guarda allí los tokens, de modo que el navegador solo recibe una cookie de sesión HttpOnly. Si la SPA tiene que conservar un token, mantenlo en memoria y no en localStorage, usa vidas cortas con rotación de refresh tokens y asume que cualquier inyección de script en la página puede exfiltrarlo.
¿Qué sustituyó a los grants implicit y password?
La práctica recomendada actual elimina ambos. Las aplicaciones de navegador usan el authorization code flow con PKCE, que nunca pone un token en el fragmento de una URL ni lo expone al historial del navegador o a los referrers. Las aplicaciones first-party que usaban el resource owner password credentials grant deben redirigir al servidor de autorización, para que la autenticación multifactor, el step-up y el consentimiento sigan centralizados en un único lugar.