[ASP.NET Identity] I: Introducción

La irrupción de ASP.NET ha supuesto una ruptura y un giro hacia una nueva modalidad de evolución del producto. Disponemos del código fuente de ASP (al menos en su totalidad) y un ciclo de versiones mucho más dinámico y no anclada a una versión del framework dedicado. En esta serie de artículos voy a dedicarlo a analizar la parte de Identity puesto que supone grandes cambios respecto a las versiones anteriores de Membership.

Como novedades más destacadas tenemos:

  • Aplicable a todo ASP.NET (Webforms, MVC, WebApi, SignalR, etc)
  • Mucho menos acoplado
  • Por lo tanto, más fácil de testear.
  • Soporte de roles, de claims, oauth, WAAD,etc.
  • OWIN
  • Basado en NuGet

Todo esto ha supuesto básicamente romper y hacer de nuevo la parte de Membership, quedando como resultado este diagrama de dependencias:

A través de este esquema vemos un par de cosas básicas:

  • La implementación de Identity está basada en la implementación de Microsoft.OWIN.
  • Para dar soporte a toda la generación, conexión y gestión de datos hace uso de EntityFramework.

Estas dos últimas características unidas a las anteriores suponen una ruptura del paradigma respecto a Membership. Mientras que antes teníamos que “colocar con calzador” nuestras entidades de usuario a Membership para poder trabajar con ella, ahora podemos usar nuestras entidades y poder trabajarlas sin tener que estar tan acopladas a un sistema de autenticación u otro.

Es decir, mientras antes teníamos que usar WebSecurity para todo ahora tenemos un UserManager que dispone de toda la funcionalidad. El concepto de manager acude a nosotros como la parte encargada de gestionar la funcionalidad completa y nos permitirá hacer todo lo posible con el usuario (login, creación, borrado, asignar roles, claims, obtener datos, etc). Para ello hará uso de una UserStore que será quien por debajo implemente toda la funcionalidad necesaria. Estamos separando el manager que usaremos para la gestión de quién se encarga de ejecutar las tareas:

   1: public class UserManager<TUser> : IDisposable where TUser : IUser

   2: {

   3:        public UserManager(IUserStore<TUser> store);

   4:        ...

   5: }

Aquí vemos que nuestro Manager depende de un TUser que implementa la interfaz IUser. Vamos por partes…

IUser

Nuestro TUser será un usuario. Y este usuario no es mas que cómo define Identity la entidad de usuario, basada únicamente en una clave o propiedad única e identificativa y un nombre de usuario. Tenemos dos posibles implementaciones:

  • La primera, la que incluye por defecto que es IUser donde la clave es un string.
  • O podemos extender IUser con una clave del tipo que queramos:
   1: public class MyUser : IUser<int>

   2:     {

   3:         public int Id { get; set; }

   4:         public string UserName { get; set; }

   5:     }

Podemos ir completando nuestro usuario con otra serie de interfaces, como por ejemplo IdentityUserLogin, IdentityUserRole, IdentityUserClaim… todas aquellas que sean necesarias para poder ir construyendo un usuario y una identidad hecha a medida para nuestra aplicación:

image

IUserStore

Lo siguiente que nos pide el Manager es un UserStore. Este será el encargado de proporcionar la funcionalidad que puede usar el manager. Y si vemos, el UserStore se basa en cumplir una serie de interfaces que son Stores que suministran funcionalidad. La más básica de todas es una IUserStore:

   1: public interface IUserStore<TUser, in TKey> : IDisposable where TUser : class, IUser<TKey>

   2:    {

   3:        Task CreateAsync(TUser user);

   4:        Task DeleteAsync(TUser user);

   5:        Task<TUser> FindByIdAsync(TKey userId);

   6:        Task<TUser> FindByNameAsync(string userName);

   7:        Task UpdateAsync(TUser user);

   8:    }

Al igual que el caso anterior, podemos optar por usar la UserStore suministrada por defecto o usar una nuestra. Y del mismo modo que en el caso anterior, vamos añadiendo interfaces a nuestra UserStore para adoptar la funcionalidad deseada:

   1: public class UserStore<TUser> : UserStore<TUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>,

   2:  IUserStore<TUser>, IUserStore<TUser, string>, 

   3: IDisposable where TUser : IdentityUser

   4: {

   5: ...

   6: }

image

Así pues, tenemos el siguiente esquema final:

image

Donde primero, definimos nuestra entidad de dominio (por ejemplo, MyUser). Este será usado por una Store (por ahora la UserStore) y esta Store, por el UserManager.

¿Qué ventajas aporta este modelo frente al antiguo? Evidentemente, todo mucho más desacoplado y testeable. Tenemos un control absoluto de las entidades y la funcionalidad que queremos dar a nuestra identidad.