OAuth 2.0: autorización delegada, no autenticación
Cómo OAuth 2.0 permite que una app actúe en tu nombre sin tu contraseña — los cuatro roles, access vs refresh tokens, los grant types y cuándo usar cada uno (Authorization Code + PKCE, Client Credentials, Device Code, Refresh Token; los deprecados Implicit y ROPC), scopes y consentimiento, y qué cambia OAuth 2.1. Más el punto crítico: OAuth autoriza al cliente, no autentica al usuario.
Una pregunta distinta de la que hizo SAML
SAML respondía “quién es este usuario, y ¿puede entrar a esta app?” — autenticación y SSO. OAuth responde una pregunta completamente distinta que explotó con el auge de las APIs y las apps de terceros en la década de 2010: ¿cómo puede una aplicación actuar en tu nombre contra un servicio, sin que le entregues tu contraseña?
Imagínalo concretamente. Una app de impresión de fotos quiere leer las fotos de Sara de su almacenamiento en la nube. La vieja forma terrible: Sara le da a la app de impresión su contraseña de la nube, y ahora una app cualquiera tiene control total de su cuenta para siempre. La forma OAuth: Sara es redirigida a su proveedor de nube, aprueba “deja que esta app lea tus fotos”, y la app de impresión recibe un token acotado y con expiración — nunca su contraseña, nunca acceso de escritura, nunca nada salvo las fotos. Eso es autorización delegada, y OAuth 2.0 (RFC 6749) es el framework para ello.
Los cuatro roles
OAuth define cuatro partes, y desenredarlas es la mayor parte de la batalla:
- Resource Owner: el usuario que posee los datos y otorga el acceso. Sara, en nuestro ejemplo.
- Client: la aplicación que quiere acceso en nombre del usuario. La app de impresión de fotos.
- Authorization Server (AS): el sistema que autentica al resource owner, muestra la pantalla de consentimiento, y emite los tokens. El servicio de identidad del proveedor de nube de Sara.
- Resource Server (RS): la API que guarda los datos protegidos, que acepta access tokens. La API de fotos de Sara.
flowchart LR accTitle: Los cuatro roles de OAuth 2.0 y cómo se relacionan accDescr: El resource owner (usuario) otorga consentimiento en el authorization server. El authorization server autentica al usuario y emite un access token a la aplicación cliente. El cliente presenta ese access token al resource server, que guarda la API protegida y devuelve los datos solicitados. El cliente nunca recibe la contraseña del usuario. RO[Resource Owner<br/>el usuario] -->|aprueba acceso| AS[Authorization Server<br/>emite tokens] AS -->|access token| C[Client<br/>la app] C -->|presenta token| RS[Resource Server<br/>la API] RS -->|datos protegidos| C
La separación entre Authorization Server y Resource Server es el genio silencioso del diseño. El AS es la única parte que toca las credenciales y decisiones del usuario; el RS solo verifica tokens. Por eso un authorization server puede proteger cientos de APIs, y una API puede aceptar tokens sin saber nada de cómo inició sesión el usuario.
Tokens: access y refresh
OAuth corre sobre bearer tokens — “bearer” significa que quien tiene el token puede usarlo, como efectivo. Hay dos tipos, con ciclos de vida muy distintos:
- Access token: la credencial que el cliente presenta al resource server en cada llamada a la API. Es acotado (otorga solo permisos específicos), de vida corta (minutos a una hora), y se usa constantemente — así que es el más expuesto de los dos. Si se roba, el daño está acotado por sus scopes y su vida corta.
- Refresh token: una credencial de vida más larga que el cliente guarda en privado y nunca envía al resource server. Cuando el access token expira, el cliente intercambia el refresh token en el authorization server por un access token fresco — para que el usuario no tenga que reconsentir cada pocos minutos. Se usa rara vez y se resguarda con cuidado; su robo es mucho más serio, por lo que OAuth 2.1 exige rotarlo o sender-constraint-arlo.
Cómo valida un token el resource server
Cuando llega un access token, el resource server debe decidir si es genuino y qué permite. Dominan dos modelos, y saber cuál usas importa:
- Tokens autocontenidos (JWT): el access token es un JWT firmado que el RS valida localmente — verifica la firma contra la clave pública del AS, luego el emisor (
iss), la audiencia (aud), la expiración (exp) y los scopes. Rápido y sin red, pero la revocación es débil: el token sigue válido hasta que expira. - Tokens de referencia + introspection: el access token es una cadena opaca que el RS envía al endpoint de introspection del AS (RFC 7662), que responde si está activo y qué scopes lleva. Una llamada de red por verificación, pero soporta revocación instantánea.
En ambos modelos la verificación de audiencia es innegociable: el RS debe confirmar que el token fue acuñado para él, no para otra API. Omítelo y un token filtrado de un servicio de bajo valor puede reusarse contra uno de alto valor — un fallo común en el mundo real. (Diseccionamos la estructura de un JWT en el artículo de Tokens y Formatos.)
Grant types: los flujos y cuándo usar cada uno
Un grant type (o flujo) es el procedimiento que un cliente usa para obtener un token. Elegir el correcto para tu tipo de cliente es la decisión central de diseño en OAuth.
Authorization Code + PKCE — el predeterminado para apps con usuario
Este es el flujo al que debes recurrir en apps web, de página única y móviles. El usuario se autentica en el authorization server, el cliente recibe un authorization code de vida corta vía redirección, y luego intercambia ese code por tokens en una solicitud separada — protegido por PKCE.
sequenceDiagram accTitle: Flujo OAuth 2.0 Authorization Code con PKCE accDescr: El cliente genera un code verifier de PKCE y su challenge hasheado. Redirige al usuario al authorization server con el code challenge. El authorization server autentica al usuario y muestra una pantalla de consentimiento, luego redirige de vuelta al cliente con un authorization code de un solo uso. El cliente intercambia el code más el verifier original en el token endpoint, y el authorization server valida que el verifier coincida con el challenge anterior y devuelve un access token y un refresh token. actor U as Usuario participant C as Client (app) participant AS as Authorization Server participant RS as Resource Server C->>C: Genera verifier + challenge de PKCE C->>AS: Solicitud authorize (+ code_challenge) AS->>U: Autentica + consentimiento AS->>C: Authorization code (un solo uso) C->>AS: Intercambia code + code_verifier AS->>AS: Verifica que el challenge coincida con el verifier AS->>C: Access token (+ refresh token) C->>RS: Llamada a API con access token RS->>C: Datos protegidos
PKCE (Proof Key for Code Exchange, RFC 7636) es la salvaguarda que vuelve robusto este flujo. El cliente inventa un secreto aleatorio (el verifier), lo hashea en un challenge, y envía solo el challenge al iniciar el flujo. Cuando canjea el authorization code, debe presentar el verifier original — y el AS verifica que hashee al challenge que vio antes. El efecto: aunque un atacante intercepte el authorization code (en un móvil, en el historial del navegador, en logs), no puede intercambiarlo por tokens sin el secreto que nunca vio. PKCE empezó como un arreglo para móvil y ahora es recomendado — y en OAuth 2.1 obligatorio — para todo flujo Authorization Code.
Client Credentials — máquina a máquina
Cuando no hay usuario — un servicio backend llamando a la API de otro servicio, un cron job, un microservicio — no hay a quién redirigir ni de quién obtener consentimiento. El cliente se autentica como sí mismo con sus propias credenciales y recibe un access token acotado a lo que ese servicio puede hacer. Este es el flujo correcto para la integración máquina-a-máquina, y es el único lugar donde no hay resource owner en absoluto (el cliente, en efecto, actúa por sí mismo).
sequenceDiagram accTitle: Flujo OAuth 2.0 Client Credentials accDescr: Un servicio backend se autentica directamente al authorization server usando su propio client ID y client secret, opcionalmente solicitando scopes. El authorization server devuelve un access token acotado a lo que el servicio puede hacer. El servicio luego llama a la API del resource server con ese token y recibe los datos protegidos. No hay usuario involucrado en ningún momento. participant SVC as Servicio que llama (client) participant AS as Authorization Server participant RS as Resource Server (API) SVC->>AS: client_id + client_secret (+ scope) AS->>SVC: Access token SVC->>RS: Llamada a API con access token RS->>SVC: Datos protegidos
Device Code — para cosas sin teclado
Smart TVs, sticks de streaming, herramientas CLI, dispositivos IoT: escribir una contraseña en ellos es doloroso o imposible. El Device Authorization Grant (RFC 8628) lo resuelve elegantemente — el dispositivo muestra un código corto y una URL, el usuario abre esa URL en su teléfono o laptop, ingresa el código, y aprueba ahí, mientras el dispositivo hace polling al AS hasta que llega la aprobación. El dispositivo limitado nunca maneja credenciales; el usuario se autentica en un dispositivo capaz.
sequenceDiagram
accTitle: OAuth 2.0 Device Authorization Grant (flujo Device Code)
accDescr: Un dispositivo con entrada limitada solicita un device code y un user code al authorization server, que devuelve un device code, un user code y una URL de verificación. El dispositivo muestra el user code y la URL. El usuario abre esa URL en un teléfono o laptop aparte, inicia sesión, y aprueba. Mientras tanto el dispositivo hace polling al token endpoint repetidamente hasta que el authorization server reporta la aprobación y devuelve un access token.
participant DEV as Dispositivo (TV / IoT)
participant AS as Authorization Server
actor U as Usuario (teléfono / laptop)
DEV->>AS: Solicita device + user code
AS->>DEV: device_code, user_code, verification_uri
DEV->>U: Muestra "ve a la URL, ingresa CÓDIGO"
U->>AS: Abre URL, inicia sesión, aprueba
loop Hasta aprobar
DEV->>AS: Polling al token endpoint (device_code)
end
AS->>DEV: Access tokenRefresh Token — renovar sin reconsentir
No es un flujo de “login” sino un mecanismo de renovación: el cliente intercambia su refresh token por un nuevo access token cuando el viejo expira, manteniendo viva la sesión sin molestar al usuario. Como los refresh tokens son poderosos y de vida larga, la práctica moderna es la rotación de refresh tokens — cada uso emite un nuevo refresh token e invalida el anterior, de modo que un refresh token robado y reusado es detectable.
Implicit y ROPC — deprecados, no usar
Dos grants tempranos están ahora activamente desaconsejados y eliminados en OAuth 2.1:
- Implicit grant: devolvía el access token directamente en la redirección del navegador (en el fragmento de la URL), sin intercambio de code. Filtraba tokens al historial del navegador, logs y referrers. Authorization Code + PKCE lo reemplaza incluso para apps de página única.
- Resource Owner Password Credentials (ROPC): el cliente recolecta el usuario y contraseña reales del usuario y los envía al AS. Esto derrota todo el propósito de OAuth — el cliente ve la contraseña — y rompe MFA y federación. Nunca lo uses para trabajo nuevo.
| Grant type | Úsalo para | Estado |
|---|---|---|
| Authorization Code + PKCE | Apps web, SPA y móviles (hay un usuario presente) | Preferido |
| Client Credentials | Máquina-a-máquina, sin usuario | Vigente |
| Device Code | TVs, IoT, CLIs — dispositivos con entrada limitada | Vigente |
| Refresh Token | Renovar acceso sin reconsentir | Vigente |
| Implicit | (era: SPAs) | Deprecado — usa Code + PKCE |
| ROPC | (era: legacy/first-party) | Deprecado — nunca para trabajo nuevo |
Scopes y consentimiento
Los scopes son cómo OAuth expresa el mínimo privilegio para el acceso delegado. Un scope es una cadena que nombra un permiso — photos.read, calendar.write, profile — y el cliente solicita solo los scopes que necesita. El access token se acuña llevando esos scopes, y el resource server los aplica: un token con photos.read no puede escribir.
El consentimiento es donde el resource owner ve y aprueba lo que se solicita — la pantalla de “esta app quiere: leer tus fotos”. Es el momento en que el usuario, no la plataforma, decide. Dos buenas prácticas definen un buen diseño de scopes y consentimiento:
- Solicita de forma estrecha. Pide el menor scope que haga el trabajo. Una app de impresión que solicita acceso total a la cuenta cuando solo necesita lectura de fotos es una bandera roja, y cada vez más plataformas y usuarios la rechazan.
- Haz el consentimiento honesto. La pantalla de consentimiento debe describir capacidades reales en lenguaje claro. Scopes demasiado amplios escondidos tras un consentimiento vago es cómo se engaña a los usuarios para que sobre-otorguen — la versión de autorización delegada del privilege creep.
Dos refinamientos que encontrarás en despliegues maduros afinan esto aún más:
- Autorización incremental. En lugar de exigir cada scope por adelantado, una app solicita el mínimo para empezar y pide más solo cuando una función realmente lo necesita. La primera pantalla de consentimiento se mantiene pequeña y honesta, los usuarios no se asustan por un muro de permisos, y la app tiene scope amplio solo cuando de verdad está en uso.
- Resource indicators (RFC 8707). Un cliente puede nombrar cuál resource server es el destino de un token, así el authorization server acuña un token acotado a esa única API en lugar de un token amplio válido en todas partes. Esto encoge el radio de impacto de un token filtrado y refuerza la verificación de audiencia que hace el resource server.
OAuth 2.1: volver el camino seguro el único camino
OAuth 2.1 no es un protocolo nuevo — es una consolidación que pliega diez años de guía de seguridad (mucha del OAuth Security Best Current Practice) en una sola base más limpia. Los cambios principales:
- PKCE es obligatorio para todos los flujos Authorization Code, clientes públicos y confidenciales por igual.
- El grant Implicit se elimina.
- El grant Resource Owner Password Credentials se elimina.
- Los redirect URIs deben compararse con coincidencia exacta de cadena — sin comodines, cerrando un vector de open-redirect largamente abusado.
- Los refresh tokens deben estar sender-constrained o rotarse.
El hilo conductor es la eliminación, no la adición: OAuth 2.0 ofrecía opciones inseguras que los desarrolladores seguían eligiendo, así que 2.1 simplemente las quita. Si aprendes los defaults de OAuth 2.1, aprendiste el consenso de cómo hacer OAuth de forma segura.
Esenciales de seguridad
La flexibilidad de OAuth es también su arma de doble filo; el OAuth 2.0 Security Best Current Practice cataloga los riesgos. Los esenciales:
- Valida los redirect URIs exactamente. La coincidencia laxa permite a un atacante redirigir el code o el token hacia sí mismo — históricamente el fallo OAuth más dañino.
- Usa siempre PKCE. Neutraliza la interceptación del authorization code.
- Usa el parámetro
state. Ata la solicitud a la sesión del usuario y bloquea CSRF en la redirección. - Trata los tokens como secretos. Los access tokens son credenciales bearer — solo sobre TLS, nunca en URLs ni logs, almacenados con cuidado en el cliente.
- Rota los refresh tokens y mantén cortas las vidas de los access tokens, para que el robo tenga un radio de impacto pequeño.
- Restringe la audiencia de los tokens. Acuña cada token para un resource server específico y haz que cada resource server rechace tokens no dirigidos a él, para que un token no pueda saltar de una API de bajo valor a una de alto valor.
La trampa que creó OpenID Connect
Como OAuth conseguía a una app un token “después de que el usuario inició sesión en el proveedor”, los desarrolladores asumieron que un access token válido significaba que el usuario estaba autenticado — y construyeron logins sobre eso. El problema: un access token es permiso bearer, no prueba de quién o siquiera de si hay un usuario presente. Un token acuñado para una app podía reusarse en otra; un token podía pertenecer a una máquina. Usar OAuth como autenticación produjo bugs reales de toma de cuentas.
El arreglo no fue abandonar OAuth sino agregar una capa de identidad propia encima de él — un token estándar que sí afirma quién es el usuario, para quién es, y cuándo se autenticó. Esa capa es OpenID Connect, y es el próximo artículo. Mantén la distinción nítida: OAuth autoriza al cliente; OIDC autentica al usuario. Las mismas redirecciones, la misma maquinaria de tokens, una afirmación de identidad agregada — pero un mundo de diferencia en lo que puedes concluir con seguridad de ello.
Recapitulación
OAuth 2.0 es autorización delegada, no autenticación:
- Cuatro roles: resource owner (usuario), client (app), authorization server (emite tokens), resource server (la API).
- Dos tokens: access tokens de vida corta y acotados usados en cada llamada, y refresh tokens resguardados y de vida larga intercambiados por nuevos access tokens.
- Los grant types encajan con tipos de cliente: Authorization Code + PKCE (apps con usuario), Client Credentials (máquina-a-máquina), Device Code (sin teclado), Refresh Token (renovación) — y nunca Implicit ni ROPC.
- Scopes + consentimiento entregan mínimo privilegio: solicita estrecho, consiente honesto.
- OAuth 2.1 vuelve el camino seguro el único camino: PKCE obligatorio, grants inseguros eliminados, coincidencia exacta de redirect.
- OAuth no es autenticación — un access token es permiso, no identidad, que es justo por lo que existe OIDC.
Ejercicios prácticos
Estos son prácticos — hazlos, no solo los leas:
- Observa un flujo real. Abre la pestaña de red de tu navegador y usa un botón de “Iniciar sesión con Google/GitHub” en cualquier sitio. Encuentra la solicitud
authorizee identifica los parámetrosclient_id,scope,redirect_uri,stateycode_challenge— luego encuentra dónde regresa el authorization code. - Decodifica el consentimiento. En la página de permisos de apps de un proveedor (p. ej. tus ajustes de “acceso de terceros” de Google o GitHub), elige una app y lista los scopes exactos que tiene. Decide cuáles son demasiado amplios para lo que la app hace.
- Elige el grant (caso de uso). Para cada uno, nombra el grant type correcto y justifícalo: una app de streaming en smart TV; un job de facturación nocturno que llama a una API de pagos; una SPA en React; una app de banca móvil.
- Rómpelo en papel (caso de uso). Una SPA usa el grant Implicit y coincide redirect URIs por prefijo. Describe dos ataques concretos que esto habilita y los cambios exactos de OAuth 2.1 que los cierran.
- Diseña acceso M2M (caso de uso). Dos de tus servicios backend necesitan comunicarse. Especifica el grant type, cómo se autentica el servicio que llama, los scopes que definirías, y cómo rotarías sus credenciales.
- Inspecciona un token (caso de uso). Obtén un access token del playground de desarrollador de un proveedor (p. ej. el OAuth Playground de Google). Si es un JWT, decodifícalo y encuentra
aud,scopeyexp; si es opaco, describe cómo lo validaría el resource server en su lugar. Luego enuncia qué debe verificar el resource server antes de confiar en él.
Tres preguntas para autoevaluarte
- Un desarrollador dice “usamos OAuth para iniciar sesión a los usuarios — si recibimos un access token válido, sabemos quiénes son”. Explica con precisión por qué esto es incorrecto y qué deberían usar en su lugar.
- ¿Por qué un refresh token se trata como mucho más sensible que un access token, y qué dos prácticas limitan el daño si uno es robado?
- Recorre paso a paso cómo PKCE derrota la interceptación del authorization code, y explica por qué un authorization code robado por sí solo es inútil para un atacante.
Preguntas frecuentes
¿Qué es OAuth 2.0?
OAuth 2.0 (RFC 6749) es un framework de autorización delegada: permite que una aplicación obtenga acceso limitado a los recursos de un usuario en otro servicio — leer su calendario, publicar en su feed — sin ver jamás la contraseña del usuario. El usuario aprueba un access token acotado y temporal que la app presenta a la API. Se trata de otorgar a una app permiso para actuar, no de iniciar sesión a un usuario.
¿OAuth 2.0 autentica al usuario?
No — y este es el punto más importante y más malentendido. OAuth autoriza a un cliente a acceder a recursos; no dice nada fiable sobre quién es el usuario para ese cliente. Un access token es prueba de permiso, no prueba de identidad. Las apps que intentan usar un access token de OAuth crudo como login cometen un anti-patrón bien conocido; autenticar al usuario es justo lo que OpenID Connect agrega sobre OAuth.
¿Cuál es la diferencia entre un access token y un refresh token?
Un access token es una credencial de vida corta que el cliente presenta al resource server para llamar a una API en nombre del usuario; lleva scopes y normalmente expira en minutos. Un refresh token es una credencial de vida más larga que el cliente guarda en privado e intercambia en el authorization server por nuevos access tokens cuando expiran, para que el usuario no tenga que reaprobar constantemente. Los access tokens se usan a menudo y están expuestos; los refresh tokens se resguardan.
¿Qué grant type de OAuth 2.0 debería usar?
Authorization Code con PKCE para casi todo lo orientado al usuario — apps web, de página única y móviles. Client Credentials para llamadas máquina-a-máquina sin usuario. Device Code para dispositivos con entrada limitada como TVs e IoT. Refresh Token para renovar sesiones. Evita por completo los grants Implicit y Resource Owner Password Credentials — están deprecados en OAuth 2.1.
¿Qué es PKCE y por qué es obligatorio?
PKCE (Proof Key for Code Exchange, RFC 7636) protege el flujo Authorization Code de la interceptación. El cliente genera un 'verifier' secreto, envía solo su hash a la ida, y revela el verifier al canjear el code — así un atacante que roba el authorization code no puede intercambiarlo sin el secreto. Originalmente para apps móviles, PKCE es obligatorio para todos los flujos Authorization Code en OAuth 2.1.
¿Qué cambió en OAuth 2.1?
OAuth 2.1 consolida una década de buenas prácticas en una sola base: PKCE es obligatorio para todos los flujos Authorization Code, los grants Implicit y Resource Owner Password Credentials se eliminan, los redirect URIs deben coincidir exactamente, y los refresh tokens deben estar sender-constrained o rotarse. No inventa protocolos nuevos — elimina las opciones inseguras que la gente seguía eligiendo y vuelve el camino seguro el único camino.