Implementar mecanismos de seguridad en el consumo de APIs (Application Programming Interfaces) se ha convertido en un tema fundamental y necesario para proteger los datos de nuestros sistemas y servicios. Hoy en día, un gran número de servicios en internet dependen de las APIs para funcionar, interconectarse y transferir datos. Los ataques para vulnerar estas comunicaciones son cada vez son más ingeniosos por lo que requerimos de mecanismos igual de ingeniosos para proteger nuestra información.
¿Qué nos dicen las buenas prácticas de seguridad?
Es importante utilizar protocolos TLS/SSL para cifrar los datos que se transmiten a través de la red o encriptar datos sensibles que viajan en el payload de la solicitud mediante un algoritmo de cifrado fuerte. Sin embargo, los procesos de seguridad van más allá puesto que en la mayoría de casos necesitamos controlar quiénes acceden a la información, desde dónde y por cuánto tiempo. Aquí entra en juego la autenticación y autorización de acceso a los servicios.
Entre los mecanismos de autenticación más populares tenemos al viejo conocido OAuth el cual es un protocolo que permite a las aplicaciones actuar en nombre del usuario, API Keys que son claves únicas que identifican al cliente y JWTs (JSON Web Token) usados para transmitir información segura entre las partes que se comunican.
¿Podemos implementar seguridad desde el backend?
Hoy en día, los frameworks de desarrollo backend proveen de varios mecanismos para controlar el acceso a las APIs que exponen. Por ejemplo:
- Spring Security: framework que se enfoca en proveer autenticación y autorización a las aplicaciones de java.
- Quarkus Security: framework que proporciona la arquitectura, mecanismos de autenticación, autorización y varias herramientas para crear aplicaciones de java seguras.
- Passport.js: librería de autenticación utilizada en Node.js, implementa estrategias de autenticación, OAuth y OpenID.
Si bien disponemos de procesos para controlar la seguridad de acceso a los servicios desde el backend, esto suele agregar cierta complejidad a los desarrollos y resistencia en el equipo en la adopción de estos marcos de trabajo. No olvidemos que desde el levantamiento de la arquitectura de un microservicio se debería considerar la implementación de procesos de seguridad. Pero, ¿Qué pasa con los servicios que no fueron construidos de esta manera? Pues no podemos pasarnos por alto que nuestros servicios pueden ser vulnerados, aquí es en donde utilizamos los mecanismos customizados de seguridad.
Seguridad customizada en AWS API Gateway
Vamos a ilustrarlo con un ejemplo real, parte de un proceso que implementamos en el Laboratorio Digital. Consideremos un sistema de calificación de créditos que funciona en una página WEB pública, no requiere de usuario y contraseña ya que cualquier cliente puede calificarse. Para esto, supongamos que tenemos una arquitectura en donde disponemos de 3 microservicios en la nube de AWS. Cada microservicio expone una API REST y se conectan a bases de datos SQL y NoSQL. Estos servicios están dockerizados y se despliegan con kubernetes en un clúster de EKS, poseen balanceadores de carga, HPA para autoescalado de PODs y telemetría para el monitoreo. El cliente WEB es un servicio construido con Angular, se conectan a las APIs de los microservicios a través de un API Gateway que orquesta y redirige las peticiones al servicio correspondiente. Como diagrama de alto nivel tendríamos lo siguiente:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/ArquitecturaAltoNivel-1-1024x476.png)
En términos de rendimiento y disponibilidad todo luce excelente, pero ¿Qué hay de la seguridad? Recordemos que el cliente WEB es de acceso público, por lo que con el enfoque antes descrito, cualquier usuario que sepa utilizar una herramienta para consumo de servicios como POSTMAN, podrá invocar al API Gateway para acceder a los datos de forma no autorizada en cualquier momento.
Para evitar este inconveniente podemos utilizar los Lambda Authorizers (autorizadores lambda) de API Gateway. Estos nos permiten controlar el acceso a las APIs ejecutando políticas de seguridad customizadas. Se los conoce de esta manera por que son procesos que se implementan en una función sin servidor en el entorno de ejecución de AWS. El algoritmo puede estar escrito en cualquier lenguaje o plataforma que soporte Lambda (Node.js, Python, etc). El esquema es el siguiente:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/AWSAuthorizerSchema.webp)
El flujo de autorización es sencillo:
- El cliente llama a un método en el API Gateway, por ejemplo: GET /client/1
- El API Gateway verifica que el endpoint tenga configurado un lambda autorizador.
- Ejecuta la función lambda de autorización, se pasan como parámetros los headers de la solicitud y un contexto con variables que aporten a la validación de las políticas de seguridad. Estas políticas pueden ir desde llamar a un proveedor de OAuth, consultar políticas IAM de AWS, recuperar credenciales de bases de datos o invocar servicios de seguridad que validan JWTs de autorización en los headers, etc.
- La función lambda retorna una política IAM (autorizado o denegado) y un identificador de solicitud.
- El API Gateway recibe la respuesta. Si se rechaza el acceso, devolverá un código HTTP 403 ACCESS DENIED. Si se permite el acceso, entonces procede a invocar el método del servicio y retorna la respuesta al cliente.
- De manera opcional se puede configurar el almacenamiento en caché de esta autorización para no volver a ejecutar el lambda por un tiempo determinado, por experiencia esto no es 100% recomendable.
Con este esquema podemos proteger nuestras APIs añadiendo una capa de seguridad adicional previo al consumo de los servicios internos. La implementación de la función lambda nos abre a una amplia gama de mecanismos y procesos disponibles para ejecutar autorizaciones. También nos permite configurar este mecanismo ya sea para toda la API o solo para ciertos endpoints o métodos HTTP.
Tomando como referencia la arquitectura descrita al inicio de esta sección, nuestro esquema de autorización se vería de la siguiente manera:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/LambdaAuthorizerSecuritySchema.drawio-1-1024x573.png)
Tenemos nuestra API protegida, pero ¿Qué mecanismo de autenticación puedo implementar? Recordemos que el caso de uso actual es una WEB pública para que los clientes accedan a un servicio de calificación de créditos. Para este proceso, hemos decidido utilizar JWTs, lo veremos en la siguiente sección.
Política de seguridad customizada mediante validación de JWT
JSON Web Token es un estándar abierto (RFC 7519) basado en JSON usado para la creación de tokens de acceso otorgando identidad y privilegios para garantizar la comunicación segura entre dos partes. Normalmente, los frameworks de desarrollo backend implementan por defecto funcionalidades para trabajar con JWT; por ejemplo Quarkus Smallrye JWT para Java. Si deseas conocer más acerca de la estructura de un JWT te recomiendo visitar el link oficial: https://jwt.io/
Ahora bien, necesitamos que los usuarios al iniciar un flujo en la WEB obtengan un JWT que identifique a este cliente y le habilite por un tiempo determinado las consultas a la API. La creación del JWT tiene que estar atado a parámetros que en conjunto formen un identificador único para la sesión. Por ejemplo, en el flujo de créditos hacemos que el cliente realice un proceso de prueba de vida, para esto comparamos su rostro captado en cámara contra imágenes de su cédula, si la prueba de vida es satisfactoria entonces generamos un código biométrico. A continuación procedemos a generar el JWT, para esto usamos el código biométrico, la identificación del usuario, el uuid de la transacción, el código del producto de crédito y el uuid de la pestaña en el navegador.
El proceso de generación y validación del JWT está centralizado en un microservicio: ServiceSecurity. Este servicio implementa la lógica necesaria para crear los JWTs con los parámetros que los hacen únicos para cada sesión y los almacena en un modelo de datos. Aplica también un conjunto de propiedades que permiten realizar configuraciones propias de cada producto, por ejemplo el tiempo de duración de cada JWT, bloqueo de IPs, bloqueo de identificaciones, etc. Con este enfoque, el flujo de seguridad evoluciona a lo siguiente:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/LambdaAuthorizerSecuritySchema-Fully-1-1024x584.png)
El diagrama anterior incluye también a ServiceSecurity, el proceso se describe de la siguiente manera:
- Tenemos un servicio que es el encargado de iniciar un flujo de crédito «/generate» en el servicio «FlowTracking». Este endpoint se encarga de recibir los parámetros de inicio para crear un proceso de crédito en la base de datos y solicitar un JWT al servicio de seguridad. Al ser este el punto de partida del flujo, es necesario que no esté protegido con autorizadores ya que será el encargado de generar la seguridad en el proceso.
- El endpoint «/generate» invoka al servicio de seguridad enviando en el body y los headers de la petición la data necesaria para generar el JWT. ServiceSecurity expone un endpoint que permiten generar los tokens. El servicio FlowTracking consume esta API mediante una red interna, es decir, consume directamente a la API del ServiceSecurity sin tener que ir al API Gateway. Esto nos agrega un beneficio adicional en el flujo ya que el servicio de seguridad es visible únicamente para los microservicios internos y el Lambda Authorizer.
- El servicio ServiceSecurity genera el token, asigna un tiempo de caducidad, lo almacena en el modelo de datos teniendo en cuenta buenas prácticas de seguridad para estos casos (por ejemplo, usar encriptación para datos sensibles). El JWT es retornado al servicio que lo solicitó.
- El servicio FlowTracking recibe el JWT, crea el proceso de crédito y retorna los datos al cliente WEB.
- El cliente WEB recibe el JWT, lo almacena en un Session Storage o en Cookies para utilizarlo en llamadas posteriores.
- El cliente WEB, para consumir servicios protegidos, agrega el JWT al campo «Authorization» de los headers de la petición. Luego, procede a llamar al método del API Gateway.
- Cada llamada a un endpoint protegido ejecutará primero la validación en el Lambda Authorizer, esta función tiene implementado un algoritmo que consume el endpoint de validación del ServiceSecurity. Es decir, el autorizador invoca al método «/validate» del servicio de seguridad.
- El ServiceSecurity ejecuta un conjunto de políticas de seguridad para validar el JWT (por ejemplo, que el token exista, que no esté expirado, que esté atado a la cédula que solicita, etc). Si está autorizado devolverá un HTTP 200 OK, caso contrario retornará un HTTP 403 ACCESS DENIED acompañado de un body en donde se incluya información adicional (por ejemplo, un código y mensaje asociados a la política de seguridad que incumple)
- El Lambda Authorizer recibe la respuesta, de ser autorizada retorna la política IAM de AUTHORIZED o DENIED en su defecto.
- Finalmente el API Gateway recibe la respuesta de autorización permitiendo ejecutar la llamada al endpoint del servicio solicitado o retornando un HTTP 403 al cliente WEB.
Configuración de Lambda Authorizer en API Gateway
Ahora vamos a configurar el autorizador en el API Gateway. Como primer paso, es necesario crear nuestra función Lambda. Hemos decidido utilizar Node.js para implementar el algoritmo de validación. Como mencionamos antes, esta función ejecuta el endpoint de validación del microservicio de seguridad pasándole el JWT recibido en los headers, según la respuesta del servicio se autorizará o denegará el acceso al endpoint destino.
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/Configuracion5-LambdaAuthorizer-1-1024x385.png)
Ahora, en el API Gateway será necesario ingresar al API que queremos proteger y a continuación dirigirnos a la sección «Authorizers»:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/Configuracion1-APIGateway-1024x257.png)
En esta sección se van a listar todos los autorizadores que hemos creado. Exacto, podemos tener varias funciones de validación para un mismo API Gateway, cada función puede implementar una estrategia de seguridad diferente según el endpoint y el método HTTP al cual queramos asignar. Para este ejemplo tenemos un autorizador:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/Configuracion2-Authorizer-1024x307.png)
La configuración del autorizador es fundamental en este proceso. Es necesario asignarle un nombre, seleccionar la región y la función Lambda que creamos en pasos anteriores para este fin. Es importante que el autorizador tenga permisos de ejecución sobre la función lambda, para ello hay que crear un rol o seleccionar un existente y especificarlo en la configuración. La función de autorización recibe parámetros para realizar la validación, en este caso lo vamos a hacer con un payload de tipo «Request», esto nos habilita una sección en la parte inferior en donde especificamos los parámetros de los headers que queremos validar, para este caso usaremos el «Authorization», actualmente usamos autorizaciones en los headers de tipo «Bearer Token».
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/Configuracion3-AuthorizerConfiguration.png)
Recomiendo desactivar el caché de la autorización, esto nos permitirá que la evaluación de las políticas de seguridad se hagan siempre en línea, es importante para los casos en los que requerimos validar un token de sesión que expira en una cantidad de tiempo predefinida (por ejemplo, 10 minutos) pero el caché de autorización almacenó un «OK» milisegundos antes de que el token caduque, entonces si una petición ingresa después, ésta recibirá un «OK» ante un token expirado por que la validación se obtuvo de la caché en lugar de volver a ejecutar la política de seguridad.
Ahora nos dirigimos a la sección «Resources» del API Gateway, aquí se listan los endpoints disponibles. Seleccionamos el endpoint que queremos proteger y el método HTTP sobre el cual va a actuar. Aquí podemos visualizar que en la sección «Authorization» ya refleja nuestro autorizador. Esta asignación se la puede hacer dando click en el botón «Edit» o a través de una plantilla de creación de API Gateway con Terraform.
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/Configuracion4-AddAuthorizerToMethod-1-1024x458.png)
Y ¡wualá!, hemos protegido un endpoint para que se validen políticas de seguridad antes de que pueda entregar información a personas que están actuando como impostores en el consumo de los servicios. El mismo proceso se puede replicar para todos los endpoints de la API; sin embargo, para hacerlo de manera automatizada se pueden configurar plantillas de Terraform que aprovisionen el API Gateway con todas las configuraciones necesarias, incluyendo los autorizadores.
Y ¿Cómo funciona?
Ahora vamos a ver los resultados. Iniciamos un flujo en el aplicativo WEB de créditos digitales. El aplicativo despliega un proceso de prueba de vida para generar un token a partir del código biométrico producto del análisis facial. Por protección de datos, hemos ocultado campos sensibles en las ilustraciones que se muestran a continuación.
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/ResultToken0-1024x465.png)
Al completar el análisis facial, el backend genera un JWT y lo envía como resultado a la WEB. A partir de este punto, el token viajan en el campo «Authorization» de los headers de las peticiones. Por ejemplo, podemos invocar endpoints para consultar los datos personales del cliente:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/ResultToken1-1-1024x525.png)
Si dejamos que el JWT expire, pero continuamos con el flujo de la WEB, el siguiente endpoint ya no estará autorizado, la WEB recibirá el mensaje de «Acceso denegado» y presentará el mensaje correspondiente al cliente:
![](http://wiki.apilab.bancodelaustro.com/wp-content/uploads/2024/06/ResultToken2-1024x431.png)
Conclusión
La seguridad de las APIs que exponen nuestros servicios representa un tema que no debe ser pasado por alto en todas las implementaciones de software. Los datos son sensibles y valiosos. Aquí presentamos un esquema de autorización customizada basada en JWT que aprovecha las características del API Gateway de AWS. Este funcionamiento podemos extenderlo también a APIs que no necesariamente son de tipo REST y que funcionan en HTTP, si no también en comunicaciones basadas en llamadas a procedimientos remotos (gRPC) o mediante WebSockets con Graphql. Al usar este enfoque, tenemos un amplio abanico de posibilidades para definir políticas de seguridad acordes a las necesidades del negocio.