Asegura tus recursos de API para la comunicación de máquina a máquina
Aprende cómo aprovechar OAuth 2.0 y JWT para asegurar tus recursos de API para la comunicación de máquina a máquina.
Cuando construyes un proyecto que involucra múltiples servicios, la seguridad de los recursos de la API es una preocupación crítica. En este artículo, te mostraré cómo aprovechar OAuth 2.0 y JWT para asegurar la comunicación entre servicios (de máquina a máquina) y cómo aplicar control de acceso basado en roles (RBAC) para seguir el principio del mínimo privilegio.
Comienza
Para seguir adelante, asumo que tienes los siguientes requisitos previos:
- Una cuenta de Logto Cloud o una instancia de Logto autohospedada
- Al menos dos servicios que necesitan comunicarse entre sí
Para fines de demostración, asumamos que tenemos los siguientes servicios:
- Un servicio de carrito de compras que proporciona APIs para gestionar carritos de compras
- Punto de acceso:
https://cart.example.com/api
- Punto de acceso:
- Un servicio de pagos que proporciona APIs para procesar pagos
- Punto de acceso:
https://payment.example.com/api
- Punto de acceso:
Flujo de autenticación
Ahora, nuestro servicio de carrito necesita llamar al servicio de pagos para procesar pagos. El flujo de autenticación es el siguiente:
Algunos conceptos clave en el diagrama anterior:
- JWT (RFC 7519): JSON Web Token. Véase nuestro artículo anterior para una introducción a JWT.
- JWK (RFC 7517): Clave Web JSON que se utiliza para verificar la firma de un JWT. Un conjunto JWK es un conjunto de JWKs.
- "client_credentials" grant (RFC 6749): Un tipo de concesión en OAuth 2.0. Utiliza las credenciales del cliente para obtener un token de acceso. Demostraremos los detalles en las secciones siguientes.
Cada participante en el diagrama anterior tiene un rol en el flujo de autenticación:
- Servicio de carrito: El cliente que necesita llamar al servicio de pagos. Aunque es un servicio, todavía es un cliente en el contexto de OAuth 2.0, y llamamos a tales clientes "aplicaciones de máquina a máquina" en Logto.
- Logto: El servidor de autorización OAuth 2.0 que emite tokens de acceso.
- Servicio de pagos: El recurso de API que proporciona APIs para procesar pagos.
Vamos a recorrer el flujo de autenticación paso a paso.
Configuración inicial
Para realizar el flujo de autenticación, necesitamos crear una aplicación de máquina a máquina (servicio de carrito) y un recurso de API (servicio de pagos) en Logto.
Crear recurso de API
Ya que nuestro servicio de carrito necesita estar al tanto de la API del servicio de pagos al realizar la autenticación, primero necesitamos crear un recurso de API. Ve a la Consola de Logto, haz clic en Recursos de API en la barra lateral izquierda y luego en Crear recurso de API. En el diálogo que se abre, ofrecemos algunos tutoriales para ayudarte a comenzar. También puedes hacer clic en Continuar sin tutorial para omitirlo.
Introduce el nombre del API y el identificador, por ejemplo, Servicio de pagos
y https://payment.example.com/api
, luego haz clic en Crear recurso de API.
Después de crear el recurso de API, serás redirigido a la página de detalles. Podemos dejarlo tal cual por ahora.
Crear aplicación de máquina a máquina
Haz clic en Aplicaciones en la barra lateral izquierda, y luego en Crear aplicación. En el diálogo que se abre, busca la tarjeta Máquina a máquina, luego haz clic en Comenzar a construir.
Introduce el nombre de la aplicación, por ejemplo, Servicio de carrito
, y haz clic en Crear aplicación. Se mostrará una guía interactiva para ayudarte a configurar la aplicación. Puedes seguir la guía para entender el uso básico, o hacer clic en Finalizar y listo para omitirla.
Solicitar token de acceso
Dado que se supone que las aplicaciones de máquina a máquina son seguras (por ejemplo, están desplegadas en una red privada), podemos usar la concesión "client_credentials" de OAuth 2.0 para obtener un token de acceso. Usa autenticación básica para autenticar al cliente:
- La URL de la solicitud es el punto de acceso de token de tu instancia de Logto. Puedes encontrar y copiarla en la pestaña Configuraciones avanzadas de la página de detalles de la aplicación de máquina a máquina.
- El método de la solicitud es
POST
. - El encabezado
Content-Type
de la solicitud esapplication/x-www-form-urlencoded
. - Para el encabezado
Authorization
, el valor esBasic <base64(app_id:app_secret)>
, dondeapp_id
yapp_secret
son el ID y el secreto de la aplicación de máquina a máquina respectivamente. Puedes encontrarlos en la página de detalles de la aplicación. - El cuerpo de la solicitud necesita especificar el tipo de concesión y el identificador de la API. Por ejemplo,
grant_type=client_credentials&resource=https://payment.example.com/api
.grant_type=client_credentials
: El valor constante para la concesión "client_credentials".resource=https://payment.example.com/api
: El identificador de la API del recurso que el cliente quiere acceder.- Si la aplicación necesita ser autorizada con ámbitos (permisos), también puedes especificar los ámbitos en el cuerpo de la solicitud. Por ejemplo,
scope=read:payment write:payment
. Cubriremos los ámbitos más adelante.
Aquí tienes un ejemplo de la solicitud usando curl
:
Un cuerpo de respuesta exitoso sería como:
Enviar solicitud con encabezado de autorización
Ahora tenemos el token de acceso, y podemos añadirlo al encabezado Authorization
de la solicitud al recurso de API. Por ejemplo, si queremos llamar a la API POST /payments
del servicio de pagos, podemos enviar la siguiente solicitud:
Validar JWT
Puedes notar que el servicio de pagos necesita validar el JWT usando el conjunto JWK, y puede tener una caché local de conjunto JWK para evitar obtener el conjunto JWK de Logto cada vez. Afortunadamente, debido a la popularidad de JWT, hay muchas bibliotecas que pueden ayudarte a lograr esto con varias líneas de código.
Estas bibliotecas se llaman habitualmente "jose" (Firmado y Cifrado de Objetos JavaScript) o "jsonwebtoken". Por ejemplo, en Node.js podemos usar jose para validar el JWT:
Si la validación tiene éxito, la variable payload
será la carga útil decodificada de JWT. De lo contrario, se lanzará un error.
Aplicar control de acceso basado en roles
Ahora hemos asegurado exitosamente la comunicación entre el servicio de carrito y el servicio de pagos. Sin embargo, el flujo de autenticación solo asegura que el cliente es el servicio de carrito real, pero no asegura que el servicio de carrito tenga permiso para realizar acciones en el servicio de pagos.
Digamos que queremos permitir que el servicio de carrito cree pagos, pero no que lea pagos.
Definir permisos
En Logto, "scopes" y "permissions" son intercambiables. Ve a la página de detalles del recurso de API del servicio de pagos y navega a la pestaña Permisos. Debería estar vacía ahora. Haz clic en Crear permiso, introduce read:payment
como nombre del permiso e introduce Leer pagos
como descripción del permiso. Luego haz clic en Crear permiso.
Repite los pasos anteriores para crear otro permiso con el nombre write:payment
y la descripción Crear pagos
.
Crear rol de máquina a máquina
Un rol es un grupo de permisos. En Logto, las aplicaciones de máquina a máquina pueden asignarse roles para otorgar permisos. Haz clic en "Roles" en la barra lateral izquierda, y luego en Crear rol.
- Introduce
checkout
como nombre del rol e introduceServicio de caja
como descripción del rol. - Haz clic en Mostrar más opciones. Elige "Rol de aplicación de máquina a máquina" como tipo de rol.
- En la sección "Permisos asignados", haz clic en el icono de flecha a la izquierda del nombre del recurso de API (Servicio de pagos) y selecciona el permiso
write:payment
. - Haz clic en Crear rol.
- Como ya tenemos una aplicación de máquina a máquina (Servicio de carrito), podemos asignarle directamente el rol en el siguiente paso. Marca la casilla a la izquierda del nombre de la aplicación (Servicio de carrito) y haz clic en Asignar aplicaciones.
Solicitar token de acceso con ámbitos
Además de los parámetros del cuerpo de la solicitud que mencionamos en Solicitar token de acceso, también podemos especificar los ámbitos en el cuerpo de la solicitud. Por ejemplo, si queremos solicitar el permiso write:payment
, podemos enviar la siguiente solicitud:
Para solicitar múltiples ámbitos, puedes separarlos con espacios. Por ejemplo, scope=write:payment read:payment
.
Validar ámbitos
Si una acción necesita el permiso write:payment
en el servicio de pagos, podemos validar los ámbitos asegurándonos de que el reclamo scope
de la carga útil JWT:
Conclusión
Si quieres proteger el acceso al servicio de carrito, también puedes aplicar el mismo flujo de autenticación. Esta vez, el servicio de carrito es el recurso de API, y el cliente es otro servicio que necesita acceder.
Con Logto, tus recursos de API están asegurados con OAuth 2.0 y JWT, y puedes seguir el principio del mínimo privilegio aplicando control de acceso basado en roles. Además, también puedes usar Logto para gestionar tus usuarios y sus permisos, e incluso integrar con proveedores de identidad de terceros.