Skip to content
This page has been auto-translated and may contain errors.View in English

OAuth e identidad delegada

Todo botón de "Iniciar sesión con Google" es una decisión de no implementar un sistema de contraseñas propio.

Tu app no verifica ninguna contraseña, no confirma quién es la persona y nunca almacena una credencial. Le pregunta a un proveedor en el que ya confía, y acepta la respuesta.

El protocolo detrás de esto es OAuth, y el flujo tiene más piezas en movimiento de las que el botón sugiere.

Dos autorizaciones distintas

Hay algo que confunde a casi todo el mundo, y vale la pena aclararlo antes de seguir con el flujo.

Ya viste la pantalla de consentimiento: una app que pide tu nombre, tu foto de perfil y tu correo electrónico. A veces pide más, a veces mucho más, como contactos, calendario y todos los mensajes de tu bandeja de entrada.

Esa pantalla es de autorización, y no tiene nada que ver con los roles de tu app. Decide a qué partes de la cuenta de Google del usuario puede acceder tu app, y esos permisos se llaman scopes.

Tus propios roles de administrador y usuario se siguen decidiendo dentro de tu back end, después de que termina el login, tal como en los capítulos anteriores.

OAuth autoriza, OIDC identifica

En rigor, OAuth otorga una autorización delegada: permiso para que tu app actúe sobre un recurso. La capa de identidad construida encima de eso es OpenID Connect, u OIDC, y los botones de "iniciar sesión con" son OIDC.

Esta distinción importa en el código. Un access token de OAuth dice a qué puede acceder tu app; un ID token de OIDC dice quién es el usuario. Tratar el primero como prueba de identidad es un error común y real.

JunoDos autorizaciones distintas Aquí se están decidiendo dos cosas, y todo el tiempo se mezclan.

Google decide qué datos de tu cuenta de Google puede ver la app. Tu app decide qué puede hacer esa persona una vez que entra. Preguntas distintas, lugares distintos, respuestas distintas.

JunoDos autorizaciones distintas Los scopes se configuran cuando armas el proveedor, no por usuario, así que es una decisión que se toma una sola vez y rara vez se vuelve a revisar. Así es como terminan las apps pidiendo mucho más de lo que usan.

Vale la pena ponerlo en una checklist: antes de lanzar una integración con OAuth, lista cada scope que pides y nombra la función que lo necesita. Todo lo que no tenga una función asociada, se elimina.

JunoDos autorizaciones distintas La versión práctica de la distinción de OIDC está en qué token lees. El ID token es un JWT con datos (claims) sobre el usuario, pensado para tu app, y es el que hay que verificar y usar.

El access token está pensado para las APIs del proveedor, y tu app debe tratarlo como algo opaco.

Leer la identidad a partir de un access token funciona hasta que el proveedor cambia su formato, porque nunca te prometieron mantener esa estructura.

Además, abre la puerta al problema del "diputado confundido" (confused deputy): un access token emitido para otra app se le presenta a la tuya y esta lo acepta, porque nada en él indica para quién estaba destinado.

Para eso existe el claim aud del ID token, y verificarlo no es opcional.

El ida y vuelta

Ocho pasos, y en medio de ellos el usuario sale de tu app:

  1. El usuario presiona el botón en tu app.
  2. Tu app lo redirige a Google.
  3. El usuario inicia sesión, directamente con Google. Tu app no ve nada de esto.
  4. Google muestra la pantalla de consentimiento para los scopes que pediste.
  5. Si el usuario acepta, Google le envía a tu servidor un código de aprobación de un solo uso. Este código no inicia sesión de nadie. Representa el consentimiento, nada más.
  6. Tu servidor envía ese código directamente de vuelta a Google, junto con las credenciales secretas de tu propia app.
  7. Esas credenciales le prueban a Google que la solicitud vino de tu back end.
  8. Google responde con un ID token que dice quién es el usuario, y tu app inicia su sesión.

La entrega en dos pasos entre el cinco y el ocho es la parte que vale la pena entender bien. El código viaja a través del navegador del usuario y por sí solo no sirve de nada; el token que realmente importa se intercambia entre servidores, donde ningún navegador lo llega a ver.

JunoEl ida y vuelta El redireccionamiento de ida y vuelta es todo el truco. Tu app deliberadamente no está presente mientras se escribe la contraseña, así que no hay nada que pueda quedarse guardado por accidente.

Ahí está la gracia: no puedes filtrar una credencial que nunca recibiste.

JunoEl ida y vuelta Los pasos cinco y seis responden una pregunta que la gente hace apenas empieza: ¿por qué primero un código, en lugar de que Google mande el token directamente?

Porque el código pasa por el navegador, donde las cosas quedan registradas y se comparten. Es de corta duración y por sí solo vale poco.

El intercambio del paso seis ocurre entre servidores con tu secreto adjunto, así que el token que realmente vale nunca toca el navegador.

JunoEl ida y vuelta Dos agregados que la lección no cubre, ambos ya estándar hoy en día. PKCE, Proof Key for Code Exchange, hace que el cliente genere un secreto antes del paso dos y lo presente en el intercambio, de modo que un código robado no pueda ser canjeado por quien lo tomó.

Empezó como una solución para móviles, y las recomendaciones actuales lo aplican en todos los casos.

Y el parámetro state: un valor aleatorio que envías en el paso dos y verificas cuando el usuario regresa, lo cual vincula la respuesta con la solicitud que iniciaste. Sin él, un atacante puede completar un flujo a su elección en el navegador de la víctima y vincular su propia cuenta a la sesión de la víctima.

Ambos son fáciles de implementar. Ambos suelen faltar en integraciones hechas a mano, lo cual es el argumento más fuerte para usar una librería mantenida en lugar de escribir el flujo tú mismo.

Tres formas en que puede fallar

Mandar al usuario fuera de tu app y confiar en que el proveedor lo devuelva abre tres brechas silenciosas.

Qué fallaPor qué importaLa solución
1Pedir demasiadoLos scopes que van más allá de lo que la app usa hacen que una filtración exponga mucho más de lo debido, y los usuarios pierden confianza en una app que parece entrometida. Divulgación de informaciónPide el conjunto mínimo que realmente necesitan tus funciones
2Filtración del código de aprobaciónEl código aparece en la barra de URL, el historial del navegador, analytics o los logs del servidor. Cualquiera que lo vea antes de que expire puede completar el login como ese usuario. SuplantaciónMantenlo fuera de todo lo que quede registrado, e intercámbialo de inmediato
3Confiar en el ID token sin verificarloCualquiera puede escribir un token que parezca válido. Solo Google puede firmar uno de verdad. Aceptarlo sin verificar le permite a un atacante hacerse pasar por cualquiera. SuplantaciónVerifica la firma, la audiencia y la expiración antes de confiar en cualquier cosa que contenga

El punto 3 es el que echa por tierra todo el modelo. La idea de delegar era que un tercero de confianza responde por el usuario; saltarse la verificación significa que terminas confiando en quien sea que haya enviado la solicitud.

JunoTres formas en que puede fallar Aquí funciona la comparación con una nota escrita. Google le entrega a tu app una nota firmada que dice quién es el usuario.

Cualquiera puede escribir una nota. Verificar la firma es cómo sabes que esta vino de Google, y saltarse esa verificación es confiar en una letra que nunca has visto.

JunoTres formas en que puede fallar La verificación son tres controles, no uno, y el segundo es el que la gente se salta. ¿Lo firmó el proveedor? ¿Se emitió para tu app? ¿Ya expiró?

El control del medio es el claim de audiencia. Sin él, un token acuñado para otra aplicación es una firma válida de Google que no dice nada sobre si estaba destinado a ti.

JunoTres formas en que puede fallar La vinculación de cuentas es el punto delicado que la lista de errores no menciona. Vincular un login delegado con una cuenta existente por medio del correo electrónico es lo primero a lo que todos recurren, y solo es seguro cuando el proveedor verifica esa dirección. Revisa el claim email_verified, y trata su ausencia como un correo no verificado.

Si te equivocas ahí, alguien puede registrar una cuenta en un proveedor usando el correo de tu usuario, iniciar sesión a través de ese proveedor, y terminar dentro de la cuenta existente.

Lo otro que hay que planear antes del lanzamiento es qué pasa cuando el proveedor está caído o un usuario pierde acceso a él. Un login que depende solo de la delegación hace que tu disponibilidad dependa de la de ellos, y la recuperación de cuenta se convierte en una conversación sobre la política de recuperación de cuenta de otra empresa.

Ponlo a prueba

Cinco situaciones de una integración real con OAuth. Identifica el error, el riesgo y la solución.

  1. Tu app solo necesita un correo electrónico para iniciar sesión, pero la solicitud de OAuth también pide contactos, calendario y archivos de Drive.
  2. La pantalla de consentimiento advierte que tu app quiere permiso para eliminar eventos del calendario. Tu app nunca toca Calendar.
  3. Después del login, la página muestra brevemente el código de aprobación de corta duración en la URL.
  4. Tu back end recibe el ID token y lo acepta sin verificar la firma ni para quién fue emitido.
  5. Tu back end acepta un ID token que expiró hace una hora.
Compara tus respuestas
#ErrorRiesgoSolución
1Pedir demasiadoUn token filtrado expone mucho más de la cuenta del usuario de lo que la app necesitabaPide solo los scopes que una función realmente usa
2Pedir demasiadoLa misma exposición, más una pantalla de consentimiento que hace ver a la app como poco confiable antes de que nadie la haya usadoElimina el scope que no se usa
3Código de aprobación filtradoLas URLs quedan registradas, guardadas y se comparten. Quien capture el código antes de que expire puede completar el loginMantén los códigos fuera de la URL, intercámbialos de inmediato
4Confiar en el ID tokenCualquiera puede falsificar algo con forma de token. Sin verificar firma y audiencia, un atacante puede hacerse pasar por cualquier usuarioVerifica la firma y la audiencia antes de confiar en él
5Confiar en el ID tokenUn token viejo puede reutilizarse para iniciar sesión repetidamente como ese usuarioRevisa la expiración y rechaza cualquier token vencido

Los puntos dos y uno son el mismo error con consecuencias distintas, por eso vale la pena leer la lista como tres problemas y no como cinco. Los puntos cuatro y cinco también son un solo error: la verificación es un conjunto de controles, y saltarse cualquiera de ellos es saltarse la verificación.

Hacia dónde va esto

Tres modelos, cada uno resolviendo el mismo problema desde un ángulo distinto. El servidor recuerda, el cliente lleva la prueba, o un proveedor responde por ti.

Cómo elegir un modelo de identidad pone los tres uno al lado del otro y explica cuándo cada uno es la respuesta correcta.