Loupe

ASPNET Identity et personnalisation du modèle de stockage des utilisateurs

Comme je l’évoquais dans un billet précédent, la gestion des utilisateurs et de l’authentification dans une application ASP.NET a été totalement refondue avec Visual Studio 2013. Une nouvelle brique arrive en remplacement des ASP.NET membership providers : ASP.NET Identity Core.

Il s’agit d’une librairie, accessible sous la forme d’un package NuGet qui défini des interfaces pour la gestion et la sauvegarde des utilisateurs de vos applications. Plus besoin d’écrire des membership providers personnalisés donc !

image

Une fois le package installé, vous avez accès à tous les éléments nécessaires à la création de votre “user store” :

image

Concrètement, un utilisateur est défini par l’interface IUser, un rôle par l’interface IRole. Ensuite, on retrouve une notion de stores utilisateurs, avec plusieurs niveaux de granularité.

IUserStore

Offre les opérations de base pour créer, supprimer, mettre à jour et récupérer un utilisateur :

namespace Microsoft.AspNet.Identity
{
    public interface IUserStore<TUser> : IDisposable where TUser : Microsoft.AspNet.Identity.IUser
    {
        Task CreateAsync(TUser user);
        Task DeleteAsync(TUser user);
        Task<TUser> FindByIdAsync(string userId);
        Task<TUser> FindByNameAsync(string userName);
        Task UpdateAsync(TUser user);
    }
}

IUserPasswordStore

Offre les opérations pour permettre la gestion de mots de passe pour vos utilisateurs :

namespace Microsoft.AspNet.Identity
{
    public interface IUserPasswordStore<TUser> : IUserStore<TUser>, IDisposable where TUser : Microsoft.AspNet.Identity.IUser
    {
        Task<string> GetPasswordHashAsync(TUser user);
        Task<bool> HasPasswordAsync(TUser user);
        Task SetPasswordHashAsync(TUser user, string passwordHash);
    }
}

IUserRoleStore

Offre les opérations pour la gestion des rôles utilisateurs dans une application :

namespace Microsoft.AspNet.Identity
{
    public interface IUserRoleStore<TUser> : IUserStore<TUser>, IDisposable where TUser : Microsoft.AspNet.Identity.IUser
    {
        Task AddToRoleAsync(TUser user, string role);
        Task<Collections.Generic.IList<string>> GetRolesAsync(TUser user);
        Task<bool> IsInRoleAsync(TUser user, string role);
        Task RemoveFromRoleAsync(TUser user, string role);
    }
}

IUserClaimStore

Offre les opérations nécessaires à la gestion des différents claims que l’on souhaite associer à un utilisateur. Typiquement, la notion de profil telle qu’on pouvait la connaître avec les ASP.NET Membership tend à disparaître pour être remplacée par des identités basées sur des claims, beaucoup plus génériques / flexibles.

namespace Microsoft.AspNet.Identity
{
    public interface IUserClaimStore<TUser> : IUserStore<TUser>, IDisposable where TUser : Microsoft.AspNet.Identity.IUser
    {
        Task AddClaimAsync(TUser user, Claim claim);
        Task<Collections.Generic.IList<Claim>> GetClaimsAsync(TUser user);
        Task RemoveClaimAsync(TUser user, Claim claim);
    }
}

IUserLoginStore

ASP.NET Identity intègre directement la notion multi logins pour la gestion des utilisateurs, permettant d’offrir des scénarios où un utilisateur peut choisir de se connecter avec un compte local, par exemple, puis associer ses différents comptes Facebook, Microsoft, Google… Cette interface vous permet d’implémenter les différentes opérations nécessaires à la mise en place de ce scénario.

namespace Microsoft.AspNet.Identity
{
    public interface IUserLoginStore<TUser> : IUserStore<TUser>, IDisposable where TUser : Microsoft.AspNet.Identity.IUser
    {
        Task AddLoginAsync(TUser user, UserLoginInfo login);
        Task<TUser> FindAsync(UserLoginInfo login);
        Task<Collections.Generic.IList<UserLoginInfo>> GetLoginsAsync(TUser user);
        Task RemoveLoginAsync(TUser user, UserLoginInfo login);
    }
}

IUserSecurityStampStore

Offre des opérations nécessaires à la génération de hash de sécurité qui dépendent des différents paramètres de connexion d’un utilisateur (différents logins et providers, notamment).

namespace Microsoft.AspNet.Identity
{
    public interface IUserSecurityStampStore<TUser> : IUserStore<TUser>, IDisposable where TUser : Microsoft.AspNet.Identity.IUser
    {
        Task<string> GetSecurityStampAsync(TUser user);
        Task SetSecurityStampAsync(TUser user, string stamp);
    }
}

L’implémentation de ces différentes interfaces vous permet donc de créer un un store d’utilisateurs qui soit 100% compatible avec ASP.NET Identity Core.

Utilisation et personnalisation du user store Entity Framework

Lorsque vous créez un projet ASP.NET MVC dans Visual Studio 2013 avec une authentification en Individual User Account, un autre package est importé automatiquement : Microsoft.AspNet.Identity.EntityFramework. Il s’agit d’un package qui propose un store utilisateur basé sur Entity Framework. Il est maintenant possible de personnaliser très simplement le modèle de données associé.

Pour commencer, rendez-vous dans le dossier Models de l’application ASP.NET MVC créé avec Visual Studio 2013, vous retrouvez un fichier de code IdentityModel.cs :

public class ApplicationUser : IdentityUser
{
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
}

La classe ApplicationUser représente la définition de ce que doit être un utilisateur dans votre application. Elle dérive directement d’une classe IdentityUser définie dans le package EntityFramework d’ASP.NET Identity :

namespace Microsoft.AspNet.Identity.EntityFramework
{
    public class IdentityUser : IUser
    {
        public IdentityUser();
        public IdentityUser(string userName);

        public virtual ICollection<IdentityUserClaim> Claims { get; }
        public virtual string Id { get; set; }
        public virtual ICollection<IdentityUserLogin> Logins { get; }
        public virtual string PasswordHash { get; set; }
        public virtual ICollection<IdentityUserRole> Roles { get; }
        public virtual string SecurityStamp { get; set; }
        public virtual string UserName { get; set; }
    }
}

Ensuite, en vous rendant dans l’AccountController qui a été généré, vous retrouverez l’utilisation de deux éléments fondamentaux dans la mise en place d’ASP.NET Identity, UserManager et UserStore :

public AccountController()
    : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}

La classe UserManager est définie dans le package IdentityCore et utilise le UserStore défini dans le package Entity Framework Identity. C’est cette dernière classe qui implémente les différentes interfaces de store décrites plus haut dans cet article :

public class UserStore<TUser> : IUserLoginStore<TUser>, IUserClaimStore<TUser>, 
    IUserRoleStore<TUser>, IUserPasswordStore<TUser>, 
    IUserSecurityStampStore<TUser>, 
    IUserStore<TUser>, IDisposable
{

}

Du coup, toutes ces notions sont directement implémentées par Microsoft dans le package EF. D’ailleurs, si vous exécutez l’application et allez sur la page Register pour créer un compte, une base de données va être automatiquement générée pour supporter ce modèle :

image

Ce modèle étant basé sur Entity Framework code first, il est tout à fait possible de modifier, en utilisant les Db code migrations, par exemple. Pour activer les code migration, rendez-vous dans la Package Manager Console et tapez la commande suivante :

PM> Enable-Migrations

image

Un dossier Migrations a du être ajouté au projet. Celui-ci contient un fichier {TimeStamp}_IntialCreate.cs qui contient tout le code nécessaire à la création de la base de données initiale. Vous pouvez alors modifier les entités manipulé par le UserStore Entity Framework et notamment la classe ApplicationUser vue plus haut. On peut par exemple lui rajouter une adresse e-mail :

public class ApplicationUser : IdentityUser
{
    public string EmailAddress { get; set; }
}

Recompilez le projet et tapez la commande suivante pour ajouter une code migration :

PM> Add-Migration AjoutAdresseEmail

Cela a pour effet de rajouter un fichier dans le dossier Migrations, contenant les opérations nécessaires pour appliquer cette migration ou revenir en arrière :

namespace WebApplication3.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class AjoutAdresseEmail : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.AspNetUsers", "EmailAddress", c => c.String());
        }
        
        public override void Down()
        {
            DropColumn("dbo.AspNetUsers", "EmailAddress");
        }
    }
}

Vous pouvez alors appliquer la migration en tapant la commande suivante :

PM> Update-Database

Et voilà, votre base de données utilisateurs a été modifiée et peut désormais accepter un e-mail utilisateur :

image

Bien entendu, rien ne vous oblige à utiliser Entity Framework Code first pour gérer vos utilisateurs. Dans ce cas, vous pouvez créer votre propre store utilisateur, avec votre propre modèle de données et système de stockage, en choisissant le niveau de granularité souhaité, via l’implémentation des 6 interfaces décrites précédemment.

A bientôt !

Julien Clignement d'œil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus