Asegurar aplicaciones basadas en la nube con OAuth 2.0 y OpenID Connect
Una guía completa para asegurar tus aplicaciones en la nube con OAuth 2.0 y OpenID Connect y cómo ofrecer una excelente experiencia de usuario con autenticación y autorización.
Introducción
Las aplicaciones basadas en la nube son la tendencia hoy en día. Mientras que el tipo de aplicación varía (web, móvil, escritorio, etc.), todas tienen un backend en la nube que proporciona servicios como almacenamiento, computación y bases de datos. La mayoría de las veces, estas aplicaciones necesitan autenticar usuarios y autorizarlos para acceder a ciertos recursos.
Aunque es posible desarrollar mecanismos de autenticación y autorización propios, la seguridad se ha convertido en una de las principales preocupaciones al desarrollar aplicaciones en la nube. Afortunadamente, nuestra industria cuenta con estándares probados como OAuth 2.0 y OpenID Connect para ayudarnos a implementar autenticación y autorización seguras.
Este artículo se basa en los siguientes supuestos:
- Tienes un entendimiento básico del desarrollo de aplicaciones (web, móvil o cualquier otro tipo).
- Has oído hablar de los conceptos de autenticación y autorización.
- Has oído hablar de OAuth 2.0 y OpenID Connect.
Sí, con haber "oído" es suficiente para los puntos 2 y 3. Este artículo usará ejemplos del mundo real para explicar conceptos e ilustrar el proceso con diagramas. ¡Comencemos!
OAuth 2.0 vs. OpenID Connect
Si estás familiarizado con OAuth 2.0 y OpenID Connect, también puedes continuar leyendo porque cubriremos algunos ejemplos del mundo real en esta sección; si eres nuevo en estos estándares, también es seguro continuar, ya que los introduciremos de manera simple.
OAuth 2.0
OAuth 2.0 es un marco de autorización que permite a una aplicación obtener acceso limitado a recursos protegidos en otra aplicación en nombre de un usuario o de la misma aplicación. La mayoría de los servicios populares como Google, Facebook y GitHub utilizan OAuth 2.0 para el inicio de sesión social (por ejemplo, "Iniciar sesión con Google").
Por ejemplo, tienes una aplicación web MyApp que quiere acceder al Google Drive del usuario. En lugar de pedirle al usuario que comparta sus credenciales de Google Drive, MyApp puede usar OAuth 2.0 para solicitar acceso a Google Drive en nombre del usuario. Aquí tienes un flujo simplificado:
En este flujo, MyApp nunca ve las credenciales de Google Drive del usuario. En su lugar, recibe un token de acceso de Google que le permite acceder a Google Drive en nombre del usuario.
En términos de OAuth 2.0, MyApp es el cliente, Google es tanto el servidor de autorización como el servidor de recursos para simplificar. En el mundo real, a menudo tenemos servidores de autorización y recursos separados para ofrecer una experiencia de inicio de sesión único (SSO). Por ejemplo, Google es el servidor de autorización y puede tener múltiples servidores de recursos como Google Drive, Gmail y YouTube.
Ten en cuenta que el flujo de autorización real es más complejo que esto. OAuth 2.0 tiene diferentes tipos de concesiones, alcances y otros conceptos que debes conocer. Dejemos eso de lado por ahora y pasemos a OpenID Connect.
OpenID Connect (OIDC)
OAuth 2.0 es excelente para la autorización, pero es posible que notes que no tiene una forma de identificar al usuario (es decir, autenticación). OpenID Connect es una capa de identidad sobre OAuth 2.0 que agrega capacidades de autenticación.
En el ejemplo anterior, MyApp necesita saber quién es el usuario antes de iniciar el flujo de autorización. Ten en cuenta que hay dos usuarios involucrados aquí: el usuario de MyApp y el usuario de Google Drive. En este caso, MyApp necesita saber quién es el usuario de su propia aplicación.
Veamos un ejemplo sencillo, suponiendo que los usuarios pueden iniciar sesión en MyApp usando nombre de usuario y contraseña:
Dado que estamos autenticando al usuario de nuestra propia aplicación, generalmente no es necesario solicitar permiso como lo hizo Google en el flujo de OAuth 2.0. Mientras tanto, necesitamos algo que pueda identificar al usuario. OpenID Connect introduce conceptos como token de ID y endpoint de información del usuario para ayudarnos con eso.
Puedes notar que el proveedor de identidad (IdP) es un nuevo participante independiente en el flujo. Es el mismo que el servidor de autorización en OAuth 2.0, pero para mayor claridad, usamos el término IdP para mostrar que es responsable de la autenticación de usuarios y la gestión de identidades.
Cuando tu negocio crece, puedes tener múltiples aplicaciones que comparten la misma base de datos de usuarios. Al igual que OAuth 2.0, OpenID Connect te permite tener un único servidor de autorización que puede autenticar usuarios para múltiples aplicaciones. Si el usuario ya ha iniciado sesión en una aplicación, no necesita ingresar sus credenciales nuevamente cuando otra aplicación lo redirige al IdP. El flujo puede realizarse automáticamente sin interacción del usuario. Esto se llama inicio de sesión único (SSO).
De nuevo, este es un flujo altamente simplificado y hay más detalles ocultos. Por ahora pasemos a la siguiente sección para evitar sobrecarga de información.
Tipos de aplicaciones
En la sección anterior, usamos aplicaciones web como ejemplos, mientras que el mundo es más diverso que eso. Para un proveedor de identidad, el lenguaje de programación, marco o plataforma exacta que uses no importa realmente. En la práctica, una diferencia notable es si la aplicación es un cliente público o un cliente privado (de confianza):
- Cliente público: Un cliente que no puede mantener sus credenciales confidenciales, lo que significa que el propietario del recurso (usuario) puede acceder a ellas. Por ejemplo, una aplicación web que se ejecuta en un navegador (por ejemplo, aplicación de una sola página).
- Cliente privado: Un cliente que tiene la capacidad de mantener sus credenciales confidenciales sin exponerlas a los usuarios (propietarios de recursos). Por ejemplo, una aplicación web que se ejecuta en un servidor (por ejemplo, aplicación web del lado del servidor) o un servicio API.
Con esto en mente, veamos cómo se pueden usar OAuth 2.0 y OpenID Connect en diferentes tipos de aplicaciones.
"Aplicación" y "cliente" pueden usarse indistintamente en el contexto de este artículo.
Aplicaciones web que se ejecutan en un servidor
La aplicación se ejecuta en un servidor y sirve páginas HTML a los usuarios. Muchos marcos web populares como Express.js, Django y Ruby on Rails entran en esta categoría; y marcos de backend-para-frontend (BFF) como Next.js y Nuxt.js también están incluidos. Estas aplicaciones tienen las siguientes características:
- Dado que un servidor solo permite acceso privado (no hay forma de que los usuarios públicos vean el código o las credenciales del servidor), se considera un cliente privado.
- El flujo de autenticación de usuario en general es el mismo que el que discutimos en la sección "OpenID Connect".
- La aplicación puede usar el token de ID emitido por el proveedor de identidad (es decir, el proveedor de OpenID Connect) para identificar al usuario y mostrar contenido específico del usuario.
- Para mantener la aplicación segura, generalmente la aplicación utiliza el flujo de código de autorización para la autenticación del usuario y para obtener tokens.
Mientras tanto, la aplicación puede necesitar acceder a otros servicios API internos en una arquitectura de microservicios; o es una aplicación monolítica que necesita control de acceso para diferentes partes de la aplicación. Discutiremos esto en la sección "Protege tu API".
Aplicaciones de una sola página (SPAs)
La aplicación se ejecuta en el navegador del usuario y se comunica con el servidor a través de APIs. React, Angular y Vue.js son marcos populares para construir SPAs. Estas aplicaciones tienen las siguientes características:
- Dado que el código de la aplicación es visible al público, se considera un cliente público.
- El flujo de autenticación de usuario en general es el mismo que el que discutimos en la sección "OpenID Connect".
- La aplicación puede usar el token de ID emitido por el proveedor de identidad (es decir, el proveedor de OpenID Connect) para identificar al usuario y mostrar contenido específico del usuario.
- Para mantener la aplicación segura, generalmente la aplicación utiliza el flujo de código de autorización con PKCE (Intercambio de Prueba de Clave para Código) para la autenticación del usuario y para obtener tokens.
Generalmente, las SPAs necesitan acceder a otros servicios API para obtener y actualizar datos. Discutiremos esto en la sección "Protege tu API".
Aplicaciones móviles
La aplicación se ejecuta en un dispositivo móvil (iOS, Android, etc.) y se comunica con el servidor a través de APIs. En la mayoría de los casos, estas aplicaciones tienen las mismas características que las SPAs.
Aplicaciones máquina a máquina (M2M)
Las aplicaciones máquina a máquina son clientes que se ejecutan en un servidor (máquina) y se comunican con otros servidores. Estas aplicaciones tienen las siguientes características:
- Al igual que las aplicaciones web que se ejecutan en un servidor, las aplicaciones M2M son clientes privados.
- La aplicación puede no necesitar identificar al usuario; en cambio, necesita autenticarse para acceder a otros servicios.
- La aplicación puede usar el token de acceso emitido por el proveedor de identidad (es decir, el proveedor de OAuth 2.0) para acceder a otros servicios.
- Para mantener la aplicación segura, generalmente la aplicación utiliza el flujo de credenciales de cliente para obtener tokens de acceso.
Al acceder a otros servicios, la aplicación puede necesitar proporcionar el token de acceso en el encabezado de la solicitud. Discutiremos esto en la sección "Protege tu API".
Aplicaciones que se ejecutan en dispositivos IoT
La aplicación se ejecuta en un dispositivo IoT (por ejemplo, dispositivos inteligentes del hogar, dispositivos portátiles, etc.) y se comunica con el servidor a través de APIs. Estas aplicaciones tienen las siguientes características:
- Dependiendo de la capacidad del dispositivo, puede ser un cliente público o privado.
- El flujo de autenticación en general puede ser diferente del que discutimos en la sección "OpenID Connect" según la capacidad del dispositivo. Por ejemplo, algunos dispositivos pueden no tener una pantalla para que los usuarios ingresen sus credenciales.
- Si el dispositivo no necesita identificar al usuario, puede no necesitar usar tokens de ID o endpoints de información del usuario; en cambio, puede ser tratado como una aplicación máquina a máquina (M2M).
- Para mantener la aplicación segura, la aplicación puede usar el flujo de código de autorización con PKCE (Intercambio de Prueba de Clave para Código) para la autenticación del usuario y obtener tokens o el flujo de credenciales de cliente para obtener tokens de acceso.
Al comunicarse con el servidor, el dispositivo puede necesitar proporcionar el token de acceso en el encabezado de la solicitud. Discutiremos esto en la sección "Protege tu API".
Protege tu API
Con OpenID Connect, es posible identificar al usuario y obtener datos específicos del usuario a través de tokens de ID o endpoints de información del usuario. Este proceso se llama autenticación. Pero probablemente no quieras exponer todos tus recursos a todos los usuarios autenticados, por ejemplo, solo los administradores pueden acceder a la página de gestión de usuarios.
Aquí es donde entra en juego la autorización. Recuerda que OAuth 2.0 es un marco de autorización, y OpenID Connect es una capa de identidad sobre OAuth 2.0; lo que significa que también puedes usar OAuth 2.0 cuando OpenID Connect ya está en su lugar.
Recordemos el ejemplo que usamos en la sección "OAuth 2.0": MyApp quiere acceder al Google Drive del usuario. No es práctico permitir que MyApp acceda a todos los archivos del usuario en Google Drive. En su lugar, MyApp debería reclamar explícitamente a qué desea acceder (por ejemplo, acceso de solo lectura a archivos en una carpeta específica). En términos de OAuth 2.0, esto se llama un alcance.
Puede que veas el término "permiso" usado indistintamente con "alcance" en el contexto de OAuth 2.0, ya que a veces "alcance" es ambiguo para usuarios no técnicos.
Cuando el usuario concede acceso a MyApp, el servidor de autorización emite un token de acceso con el alcance solicitado. Luego, el token de acceso se envía al servidor de recursos (Google Drive) para acceder a los archivos del usuario.
Naturalmente, podemos reemplazar Google Drive con nuestros propios servicios API. Por ejemplo, MyApp necesita acceder al OrderService para obtener el historial de pedidos del usuario. Esta vez, dado que la autenticación del usuario ya está hecha por el proveedor de identidad y tanto MyApp como OrderService están bajo nuestro control, podemos omitir pedirle al usuario que conceda acceso; MyApp puede enviar directamente la solicitud a OrderService con el token de acceso emitido por el proveedor de identidad.
El token de acceso puede contener un alcance read:order
para indicar que el usuario puede leer su historial de pedidos.
Ahora, digamos que el usuario accidentalmente ingresa una URL de la página de administración en el navegador. Dado que el usuario no es un administrador, no hay un alcance admin
en el token de acceso. OrderService rechazará la solicitud y devolverá un mensaje de error.
En este caso, OrderService puede devolver un código de estado 403 Forbidden para indicar que el usuario no está autorizado para acceder a la página de administración.
Para aplicaciones máquina a máquina (M2M), no se involucra a ningún usuario en el proceso. Las aplicaciones pueden solicitar directamente tokens de acceso al proveedor de identidad y usarlos para acceder a otros servicios. El mismo concepto se aplica: el token de acceso contiene los alcances necesarios para acceder a los recursos.
Diseño de autorización
Podemos ver dos cosas importantes a considerar al diseñar la autorización para proteger tus servicios API:
- Alcances: Define qué puede acceder el cliente. Los alcances pueden ser granulares (por ejemplo,
read:order
,write:order
) o más generales (por ejemplo,order
) según tus requisitos. - Control de acceso: Define quién puede tener alcances específicos. Por ejemplo, solo los administradores pueden tener el alcance
admin
.
En cuanto al control de acceso, algunos enfoques populares son:
- Control de acceso basado en roles (RBAC): Asigna roles a usuarios y define qué roles pueden acceder a qué recursos. Por ejemplo, un rol de administrador puede acceder a la página de administración.
- Control de acceso basado en atributos (ABAC): Define políticas basadas en atributos (por ejemplo, departamento del usuario, ubicación, etc.) y toma decisiones de control de acceso basadas en estos atributos. Por ejemplo, un usuario del departamento de "Ingeniería" puede acceder a la página de ingeniería.
Vale la pena mencionar que para ambos enfoques, la forma estándar de verificar el control de acceso es comprobar los alcances de los tokens de acceso, en lugar de roles o atributos. Los roles y atributos pueden ser muy dinámicos, mientras que los alcances son más estáticos, lo que facilita mucho la gestión.
Para información detallada sobre el control de acceso, puedes consultar RBAC y ABAC: Los modelos de control de acceso que debes conocer.
Tokens de acceso
Aunque hemos mencionado el término "token de acceso" muchas veces, no hemos discutido cómo obtener uno. En OAuth 2.0, un token de acceso es emitido por el servidor de autorización (proveedor de identidad) después de un flujo de autorización exitoso.
Echemos un vistazo más de cerca al ejemplo de Google Drive y supongamos que estamos usando el flujo de código de autorización:
Hay algunos pasos importantes en el flujo para la emisión del token de acceso:
- Paso 2 (Redirige a Google): MyApp redirige al usuario a Google con una solicitud de autorización. Normalmente, esta solicitud incluye la siguiente información:
- Qué cliente (MyApp) está iniciando la solicitud
- Qué alcances está solicitando MyApp
- Dónde Google debe redirigir al usuario una vez finalizada la autorización
- Paso 5 (Solicitar permiso para acceder a Google Drive): Google solicita al usuario que conceda acceso a MyApp. El usuario puede elegir otorgar o denegar el acceso. Este paso se llama consentimiento.
- Paso 7 (Redirige a MyApp con datos de autorización): Este paso es recién introducido en el diagrama. En lugar de devolver directamente el token de acceso, Google devuelve un código de autorización de un solo uso a MyApp para un intercambio más seguro. Este código se utiliza para obtener el token de acceso.
- Paso 8 (Usar código de autorización para intercambiar por token de acceso): Este también es un paso nuevo. MyApp envía el código de autorización a Google para intercambiarlo por un token de acceso. Como proveedor de identidad, Google compondrá el contexto de la solicitud y decidirá si emitir un token de acceso:
- El cliente (MyApp) es quien dice ser
- El usuario ha otorgado acceso al cliente
- El usuario es quien dice ser
- El usuario tiene los alcances necesarios
- El código de autorización es válido y no ha caducado
El ejemplo anterior asume que el servidor de autorización (proveedor de identidad) y el servidor de recursos son el mismo (Google). Si son separados, tomando el ejemplo de MyApp y OrderService, el flujo será así:
En este flujo, el servidor de autorización (IdP) emite tanto un token de ID como un token de acceso a MyApp (paso 8). El token de ID se usa para identificar al usuario, y el token de acceso se usa para acceder a otros servicios como OrderService. Dado que MyApp y OrderService son servicios de primera parte, generalmente no solicitan al usuario que otorgue acceso; en su lugar, se basan en el control de acceso en el proveedor de identidad para determinar si el usuario puede acceder a los recursos (es decir, si el token de acceso contiene los alcances necesarios).
Finalmente, veamos cómo se usa el token de acceso en aplicaciones máquina a máquina (M2M). Dado que no se involucra a ningún usuario en el proceso y la aplicación es de confianza, puede solicitar directamente un token de acceso al proveedor de identidad:
El control de acceso todavía puede aplicarse aquí. Para OrderService, no importa quién sea el usuario o qué aplicación está solicitando los datos; solo le importa el token de acceso y los alcances que contiene.
Los tokens de acceso generalmente se codifican como JSON Web Tokens (JWT). Para obtener más información sobre JWT, puedes consultar ¿Qué es un JSON Web Token (JWT)?.
Indicadores de recursos
OAuth 2.0 introduce el concepto de alcances para el control de acceso. Sin embargo, puedes darte cuenta rápidamente de que los alcances no son suficientes:
- OpenID Connect define un conjunto de alcances estándar como
openid
,offline_access
yprofile
. Puede ser confuso mezclar estos alcances estándar con tus alcances personalizados. - Puedes tener múltiples servicios API que comparten el mismo nombre de alcance pero tienen diferentes significados.
Una solución común es agregar sufijos (o prefijos) a los nombres de los alcances para indicar el recurso (servicio API). Por ejemplo, read:order
y read:product
son más claros que read
y read
. Una extensión de OAuth 2.0 RFC 8707 introduce un nuevo parámetro resource
para indicar el servidor de recursos al que el cliente desea acceder.
En realidad, los servicios API generalmente se definen por URLs, por lo que es más natural usar URLs como indicadores de recursos. Por ejemplo, el API de OrderService puede representarse como:
Como puedes ver, el parámetro trae la conveniencia de usar las URLs de recursos reales en las solicitudes de autorización y tokens de acceso. Vale la pena mencionar que la RFC 8707 puede no ser implementada por todos los proveedores de identidad. Debes verificar la documentación de tu proveedor de identidad para ver si admite el parámetro resource
(Logto lo admite).
En resumen, el parámetro resource
se puede usar en solicitudes de autorización y tokens de acceso para indicar el recurso que el cliente quiere acceder.
No hay restricción sobre la accesibilidad de los indicadores de recursos, es decir, el indicador de recursos no necesita ser una URL real que apunte a un servicio API. Por lo tanto, el nombre "indicador de recursos" refleja adecuadamente su papel en el proceso de autorización. Puedes usar URLs virtuales para representar recursos que deseas proteger. Por ejemplo, puedes definir una URL virtual
https://api.example.com/admin
en tu aplicación monolítica para representar el recurso al que solo los administradores pueden acceder.
Reuniendo todo
En este punto, hemos cubierto los fundamentos de OAuth 2.0 y OpenID Connect, y cómo usarlos en diferentes tipos de aplicaciones y escenarios. Aunque los hemos discutido por separado, puedes combinarlos según tus requisitos comerciales. La arquitectura general puede ser tan simple como esta:
O un poco más compleja:
A medida que tu aplicación crece, verás que el proveedor de identidad (IdP) juega un papel crítico en la arquitectura; pero no se relaciona directamente con tus objetivos comerciales. Aunque es una gran idea externalizarlo a un proveedor confiable, debemos elegir el proveedor de identidad sabiamente. Un buen proveedor de identidad puede simplificar enormemente el proceso, reducir el esfuerzo de desarrollo y salvarte de posibles escollos de seguridad.
Notas finales
Para las aplicaciones modernas en la nube, el proveedor de identidad (o servidor de autorización) es el lugar central para la autenticación de usuarios, la gestión de identidades, y el control de acceso. Aunque discutimos muchos conceptos en este artículo, todavía hay muchos matices a considerar al implementar dicho sistema. Si estás interesado en aprender más, puedes explorar nuestro blog para obtener más artículos en profundidad.