
Un año más diciembre toca a su fin, y junto a el él último escrito de una tercera temporada en la que la serie de artículos centrados en la construcción de una landing zone sobre AWS, o la preparación de alguna que otra certificación cloud de Amazon y Google Cloud, han brillado con luz propia.
A la espera de saber lo que nos deparará el 2023, no se me ocurre mejor manera de despedir el año que con un ensayo acerca de la gestión de los atributos de los usuarios alojados en Amazon Cognito. Si bien puede parecer un tópico un tanto trivial, al fin y el cabo, se trata de una funcionalidad ofrecida por el propio servicio de AWS, pronto descubriréis que no es oro todo lo que reluce, y que bien merece la pena invertir algo de tiempo en diseñar un sistema que permita una gestión segura de dicha información.
Si más dilación, comenzamos.
Amazon Cognito
Tal y como reza la documentación oficial de AWS, Amazon Cognito es un servicio gestionado que ofrece autenticación, autorización y administración de usuarios para aplicaciones móviles y web, permitiendo iniciar sesión directamente con un nombre de usuario y una contraseña, a través de un tercero como Facebook, Amazon, Google y Apple, o por medio de proveedores de identidad SAML y OIDC.
No hay mucho mas que explicar, se trata de un sistema para la gestión de usuarios que puede actuar a modo de identity provider, almacenando la información de los usuarios directamente en su base de datos, o como proxy de federación con lo sistemas externos previamente descritos.
Sus principales virtudes son la facilidad de uso, la ausencia de mantenimiento de la infraestructura, su reducido coste ($0.0055 por cada usuario mensual activo) y como no podía ser de otra forma, su integración con los servicios propios de la nube, como el API Gateway o el Application Load Balancer.
User custom attributes
Una de sus características mas interesantes es que, independientemente del método de login empleado, Cognito siempre almacena un perfil del usuario en su User Pool, de forma que pueda ser enriquecido mediante atributos personalizados añadidos manualmente, o importados automaticamente desde el identity provider externo.
Así, estos atributos no son mas que fragmentos de información como por ejemplo, el nombre, la dirección de correo electrónico, el número de teléfono o un identificador interno, que son embebidos en el ID token cuando el usuario es autenticado, para que posteriormente puedan ser explotados tanto por el frontend como por el backend durante su operativa.
Antes de detallar cual es la problemática asociada a la gestión de estos atributos, y por lo tanto, la razón de ser de este artículo, una pequeña aclaración: Cuando un usuario inicia sesión contra Amazon Cognito, este devuelve un Access token, un ID token y un Refresh token.
Así, el Access token contiene información acerca del usuario autenticado, una lista de los grupos de usuarios y una lista de ámbitos, mientras que el ID Token contiene datos acerca de la identidad del usuario autenticado, como por ejemplo, el nombre, el correo electrónico, el número de teléfono o cualquiera de los atributos personalizados previamente definidos. Si bien ambos tokens pueden ser utilizados para autenticar al usuario, Amazon recomienda emplear el ID token para autorizar aquellas operaciones en función de la identidad del usuario que inició sesión, que es el caso de uso que se pretende abordar en el presente artículo.
Problematic
Sin más preámbulos, el problema (crítico) que presenta Cognito cuando se emplea para gestionar los metadatos de los usuario dados de alta en el sistema, es que, una vez definido el esquema, este no puede ser modificado. Es decir, una vez creado el user pool, no es posible modificar su configuración para agregar nuevos atributos, sean o no requeridos, modificar el nombre de los ya existentes, o hacer que dejen o no de ser requeridos.
Por lo tanto, el esquema de datos debe ser definido a la perfección desde las primeras fases del proyecto, al menos si no se quiere recrear el dichoso user pool desde cero, para lo que por cierto, AWS no ofrece ninguna funcionalidad nativa de exportación o backup.
Este es precisamente el segundo gran problema que presenta Cognito, ya que en caso de desastre (natural o humano), no dispone de una herramienta con la que almacenar, recuperar y restaurar los datos en una nueva instancia o región. Algo totalmente incomprensible en un servicio básico de AWS empleado a diario por cientos de organizaciones a lo largo del mundo entero.
Solution
AWS, en un intento de paliar las carencias de su producto, ha detallado una arquitectura de referencia con la que de alguna forma facilitar la exportación de los datos de los usuarios. Desgraciadamente, esta solución no solo es compleja, sino que tambiénpresenta algunas importantes limitaciones, como el hecho de no ser compatible con autenticaciones basadas en MFA, o no ser capaz de gestionar los atributos de aquellos usuarios que hagan login a través de un identity provider externo.
Por ello, la solución propuesta en este artículo es mucho menos ambiciosa pero también mas sencilla: almacenar los atributos de los usuarios en una DynamoDB, e inyectarlos en el ID token a través de una función Lambda en el pre-token generation trigger, que se desencadena cuando el usuario es autenticado satisfactoriamente. Simple y eficaz.

Por partes, DynamoDB es un sistema NoSQL clave-valor, escalable, multiregión y completamente administrado, especialmente construido para aquellos sistemas de baja latencia que requieran que al menos el 99.9% de las operaciones de lectura y escritura se realicen en unos pocos milisegundos.
Perfecto para el caso de uso descrito, ya que proporciona un rendimiento sin parangón, a la vez que garantiza su disponibilidad a través de una o múltiples regiones. Y si, dispone de funcionalidades nativas de backup, ademas de ser compatible con las herramientas de observabilidad de AWS, como CloudWatch, CloudTrail o X-Ray.
Por otro lado, las funciones Lamda permiten ejecutar pequeños fragmentos de códigos o funciones stateless, durante un determinado periodo de tiempo, sin necesidad de aprovisionar ni administrar servidores. Dado que es la propia AWS quien se encarga aprovisionar nuevas instancias en función de la carga de trabajo, la escalabilidad y disponibilidad están garantizadas, a la vez que se mantienen los costes a raya, pues solo se nos facturará en base al tiempo de ejecución.
El coldstart tampoco supone un problema, ya que, para el caso de uso descrito, las funciones Lambda no tienen que estar embebidas en ninguna VPC privada, el principal motivo del incremento del tiempo de inicialización. Ademas, como DynamoDB funciona a través del protocolo HTTP, la gestión de las conexiones con la base de datos tampoco supondrá un impedimento. Sobra decir que también son compatibles con las herramientas de observabilidad de AWS, como CloudWatch, CloudTrail o X-Ray.
En definitiva, ambos servicios no solo congenian a la perfección, sino que también se ajustan a lo que la problemática requiere: disponibilidad, rendimiento y backup.
Finalmente, comentar que Cognito también dispone de un webhook denominado Post confirmation Lambda trigger, con el que almacenar los atributos del usuario en DynamoDB cuando estos son dados de alta por primera vez en el sistema.

Conclusiones
En conclusión, Cognito presenta algunos problemas cuando se emplea para gestionar los metadatos de los usuario, como el hecho de que el esquema de atributos no puede ser modificado, o que no dispone de capacidades nativas para el backup de los datos.
Por ello, en este escrito se propone una solución basada en almacenar los atributos de los usuarios en una DynamoDB, e inyectarlos en el ID token a través de una función Lambda cuando el usuario es autenticado satisfactoriamente.
Referencias
Se recomienda encarecidamente leer los siguientes artículos que han servido de base para el escrito:
- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html
- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
- https://docs.aws.amazon.com/solutions/latest/cognito-user-profiles-export-reference-architecture/welcome.html