ASP .NET Core Identity en Cosmos DB

La mejor forma de manejar la autorización y autenticación dentro de una aplicación web con ASP .NET Core es por medio de ASP.NET Core Identity. Es la evolución de lo que se conocía como Membership Provider en ASP .NET.

Entre otras mejoras Identity permite interactuar con múltiples protocolos de autorización y autenticación sobre HTTP.

Dentro del flujo de autorización es necesario interactuar con el sistema de persistencia que tenemos implementado en nuestra aplicación web que naturalmente estará basado en usuarios y roles. La implementacion mas común que veremos de Identity es ocupar una base SQL (SQL Server/Azure) con Entity Framework.

Haremos una implementación de este proveedor de identidad para que se integre con Cosmos DB.

Identity Core 2.0

Solo hagamos un breve repaso de los objetos que expone el proveedor (Microsoft.AspNet.Identity) que ocuparemos para nuestra implementación:

UserManager<TUser>: Esta clase principalmente tiene la responsabilidad de controlar todos los atributos del usuario contra el motor de persistencia, esta clase tiene una dependencia con IUserStore<TUser>. Que asume deberá ser inyectada en el constructor.

IUserStore<TUser>: Esta interfaz contiene todas las firmas con las operaciones básicas (para usuarios) que se deberán ejecutar sobre el proveedor de persistencia.

SignInManager<TUser>: Esta clase tiene la responsabilidad de controlar todo lo que tiene que ver con la autenticacion sobre cualquier protocolo, es la encargada de determinar si el usuario tiene los atributos para autenticarse de forma exitosa en la aplicación y también es la encargada de establecer la sesión sobre el protocolo que se determine (Cookies, Issued Tokens, JWT). Tiene dependencia con UserManager<TUser>.

RoleManager<TRole>: Cuando nuestra aplicación requiere roles, esta clase tiene la responsabilidad de controlar todos los atributos de los roles contra el motor de persistencia. Tiene dependencia con IRoleStore<TRole>.

La mayoría de estas clases e interfaces son genéricas pues permiten establecer una entidad POCO para definir las propiedades del usuario y rol.

Existen mas clases dentro del proveedor pero por ahora solo ocuparemos las mencionadas.

ASP .NET Identity Core – Cosmos DB

Integrando Cosmos DB

Lo primero que tenemos que hacer es implementar nuestra propia versión de IUserStore<TUser> y de IRoleStore<TRole>. Para esto tambien requerimos definir nuestra entidad que representa al usuario, que se veria algo asi:

En el código podemos ver que estamos heredando de IdentityUser<TKey> que nos permite tomar propiedades comunes para nuestra entidad como: Id, nombre de usuario, correo, email, contraseña, etc. No es obligatorio hacer esta herencia, podríamos omitirlo y nosotros establecer las propiedades que necesitemos. Unas propiedades si las necesitaremos de forma predefinida. Ya lo veremos

También tenemos una propiedad con el nombre de Tenan,  esta se ocupa en el caso donde queramos tener una partición en la colección de usuarios para la base de Cosmos DB. Por ahora esta en duro pues solo es para ejemplificar.

La implementacion de IUserStore<TUser> se podría referir a nuestro repositorio de usuarios dentro de nuestra aplicación, así es que lo que vamos a hacer es incluir la implementacion de esa interfaz en nuestra misma interfaz que define nuestro de repositorio de usuarios.

La definición del repositorio de usuario se podría ver así:

Estoy definiendo mi propia interfaz de IUserRepository que expone 4 métodos propios y adicional esta interfaz debe implementar IUserStore<User>. Adicional estoy agregando otras interfaces a implementar.

IUserPasswordStore<User>: Esta interfaz se requiere para definir como se obtendrá la propiedad que contiene la contraseña con hash. Es importante pues se ocupa a momento de autenticar por medio de contraseña al usuario.

IUserRoleStore<User>: Esta interfaz se requiere para controlar como se asocian los roles a los usuarios. Esto es porque en nuestra aplicación esta basada en usuarios y roles. Si no requerimos roles se puede omitir.

La clase de la implementacion de IUserRepository queda bastante extensa pero se veria algo asi:

Hasta este punto no hemos escrito aun nada de código que interactúe con Cosmos DB. En la clase UserRepository podemos ver que hereda de BaseRepositoryCosmos<User> que es donde se hace la interacción con Cosmos DB. La clase base  BaseRepositoryCosmos<User> encapsula la lógica necesaria para crear el cliente, crear la base y la colección basada en la entidad genérica que recibe; en este caso nuestra entidad User, internamente la clase base define el nombre de la entidad como el nombre de la colección para Cosmos DB.

Esta es la definición de BaseRepositoryCosmos<User>:

Para mas detalle de esta clase base para el repositorio lo explico aquí.

Como ya lo explicamos, esto nos permite interactuar con la base de Cosmos DB. Solo nos queda implementar cada unos de los métodos que requieren las interfaces que ocuparemos.

Aqui la implementacion de algunos metodos:

Los métodos FindByIdAsync FindByNameAsync como su nombre lo dice permiten buscar al usuario por id o por username, estos métodos los ocupa bastante el proveedor de Identity, pues hay varias operaciones donde tiene que ir a buscar a un usuario en especifico.

En el código de arriba podemos ver los métodos para crear y actualizar, ambos internamente ocupan un metodo de la clase base que es Upsert, ese método ejecuta la operación de UpsertDocumentAsync.

En este enlace de la solución se puede ver la implementación completa de cada método. Solución aquí.

Una vez que tenemos la implementación completa en nuestros repositorios hay que indicarle al proveedor de donde debe tomar lo que requiere.

Configurando proyecto Web (ASP .NET Core)

El proveedor de Identity se configura y se inicializa en nuestro proyecto web, aquí es donde establecemos todas las políticas y protocolos con los que el sitio web autoriza el acceso.

Previo debemos indicar a los objetos de UserManager<User> SignInManager<User> que deben de ocupar nuestro repositorio de usuario pues ahí esta la lógica para consumir nuestra colección de Cosmos DB. Para eso tenemos que hacer nuestra propia versión heredando de cada clase.

Se veria algo asi:

Aquí tenemos la clase ApplicationUserManager  que hereda de UserManager<User> y vemos que la definición del constructor requiere de muchas dependencias. La que nos interesa es la primera, ahí es donde le indicamos que debe de utilizar nuestro repositorio definido sobre la interfaz IUserRepository.

Las demás dependencias las dejamos igual, internamente el motor de inyección de dependencias se encargara de resolverlas.

Ahora vemos la clase ApplicationSignInManager que hereda de SignInManager<User> y realizamos lo mismo en el constructor. Esta clase tiene dependencia con UserManager<User> así es que le indicamos que la clase que debe ocupar es nuestra definición de ApplicationUserManager.

Con esto logramos indicar al proveedor que debe de ocupar nuestro repositorio que se conecta a Cosmos DB.

Ahora debemos de indicar al motor de inyección de dependencias de ASP .NET Core el registro y configuración de todas nuestras dependencias. Esto lo hacemos en el archivo Startup.cs como naturalmente se hace.

Primero indicamos que nuestro método sera por medio de Cookies.

Hacemos el registro de todas nuestras dependencias. También indicamos como resuelve la configuración para Cosmos DB con al clase CosmosConfiguration.

Finalmente le indicamos al proveedor Identity las dependencias que debe de ocupar.

Controlador y Login

Ahora ya podemos ocupar el proveedor de forma natural sobre nuestros controladores para determinar la autenticación y autorización. Aquí tenemos el controlador que ocupamos en la vista de login.

Podemos ver como en el constructor del controlador inyectamos la dependencias de UserManager<User>SignInManager<User>. En la acción DoLogin es donde autenticamos al usuario obteniendo sus credenciales (nombre de usuario y contraseña), internamente ocupamos el método PasswordSignInAsync que recibe el nombre de usuario y contraseña.

Internamente el método PasswordSignInAsync se encarga de hacer las validaciones necesarias para determinar si autentica al usuario ocupando nuestro repositorio. Por eso es importante hacer la implementación completa de IUserStore<User> pues de forma indistinta ocupa cada unos de los métodos.

En el caso de que queramos restringir un recurso basado en roles podemos ocupar el atributo Authorize:

Internamente el proveedor de Identity ocupa nuestro repositorio para determinar si el usuario tiene el rol de “Admin”. Para esto debemos hacer la implementación correcta de IUserRoleStore<User>.

De forma básica esto es lo que se tendría que hacer para integrar el proveedor con Cosmos DB. Aquí dejo una vista de la solución en visual studio con todo lo que contiene. Y el enlace a la solucion en github.

 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *