Identity sans EF, Yes we can

2019-09-15 par Badre BSAILA

Identity est le nuget de référence pour implémenter l’authentification dans la sphère .NET Core. Il a été conçu en définissant un certain nombre d’interfaces pour qu’il inter-opère avec différentes bases de données et solutions de persistance. Aujourd’hui l’implémentation la plus poussée et utilisée est celle avec EF Core. Mais dès qu’on pense à utiliser d’autres BD non supportées par EF ou d’autres ORM, on se heurte au peu de ressources dédiées à ce sujet. Le but de ce billet est de vous montrer que le processus n’est pas si dûr que ça :smiley: et qu’il est très instructif. On s’intéressera plus particulièrement à faire fonctionner Identity en utilisant Dapper et une base de données SQL Server.

Concevoir la base de données

Nous allons partir d’un schéma semblable à celui généré par une migration EF Core à quelques différences près:

alt Schéma de BD identity

Les tables les plus importantes:

  • AspNetUsers: comme son nom l’indique, elle va contenir les utilisateurs de votre application.
  • AspNetRoles: contient les rôles de l’application, ces derniers vont limiter le scope des utilisateurs capables de faire une ou plusieurs actions.
  • AspNetUserRoles: une table d’association entre les deux précédentes, permet d’affecter les rôles à certains utilisateurs.

Vous avez sûrement remarquer l’utilisation d’une colonne ConcurrencyStamp, elle prévient les problèmes des mises à jour concurrentes conflictuelles. En gros quand deux requêtes entrantes vont essayer de modifier un utilisateur en même temps, ils vont dans un premier temps récupérer ce dernier avec son concurrencyStamp, le première mise à jour va déclencher automatiquement le changement du concurrencyStamp et va faire échouer la deuxième car son concurrencyStamp est désormais obsolète.

Création et mise à jour du schéma de base de données

Je vous propose le projet SQL server suivant qui modélise le schéma dessus et contient aussi les procédures stockées des opérations CRUD basiques. Vous pouvez l’intégrer à votre code source et l’adapter à vos propres besoins, créer des pipelines CI/CD pour mieux gérer vos évolutions et vos déploiements depuis l’environnement de dév jusqu’à la prod (sans se marcher les pâtes avec les migrations EF). Pour déployer ce projet en local, créez une base de données Identity vide et jouez la commande suivante à la racine du dossier contenant le sqlproj:

Pour vos environnement on-premise de dév/prod ou Cloud, je vous conseille d’utiliser le SqlPackage.exe.

Interopérer avec le nuget Identity sans EF Core

C’est la partie la plus intéressante du blog que vous attendiez tous :smiley:. Nous allons essayer de configurer une Web Api .NetCore 2.2 pour qu’elle utilise Identity sans passer par aucun provider. L’implémentation complète se trouve dans le lien. Par contre, je vais juste présenter l’essentiel de ce qu’il faut faire, et c’est à vous de jouer après.

Commençons:

  • Ajoutez le nuget <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" /> dans le csproj de votre api.

  • Nous allons ajouter les classes de notre modèle à savoir Role et User, l’approche la plus simple est de créer des classes qui héritent de IdentityRole<TKey> et IdentityUser<TKey>, et après ajouter les champs qui sont spécifiques à votre modèle, exemple:

  • Créez une classe repository RolesRepository pour la table AspNetRoles, elle devra implémenter l’interface IRoleStore<TRole> nécessairement pour qu’Identity puisse manipuler les rôles de ton application. L’interface contient des opérations de CRUD basiques, exemple d’implémentation de la création de nouveau rôle avec Dapper:
  • Au cas où vous aurez besoin dans votre application d’accéder aussi à la liste complète des rôles, alors votre repository doit aussi implémenter l’interface IQueryableRoleStore<TRole> qui contient une seule propriété IQueryable<TRole> Roles { get; }, exemple d’implémentation:
  • Créez une classe repository UsersRepository pour la table AspNetUsers, elle devra implémenter les interfaces IUserEmailStore<TUser>, IUserPasswordStore<TUser>, IUserRoleStore<TUser>,IUserStore<TUser> pour qu’Identity puisse manipuler les utilisateurs et leurs informations comme l’email, mot de passe et les rôles attribués. Les interfaces contiennent des opérations de CRUD basiques, exemple d’implémentation de la récupération d’un utilisateur par son login:
  • Au cas où vous aurez besoin dans votre application d’accéder aussi à la liste complète des utilisateurs, alors votre repository doit aussi implémenter l’interface IQueryableUserStore<TUser> qui contient une seule propriété IQueryable<TUser> Users { get; }, Exemple:
  • Lien 1, Lien 2, Lien 3, Lien 4, Lien 5 de l’implémentation complète du repository des utilisateurs (classes partielles).

  • Finalement il faudra configurer Identity pour qu’il utilise vos repositories fraîchement créées et vos propres modèles de données. Il suffit d’ajouter ces lignes dans la méthode Startup.ConfigureServices:

Bonus

Malheureusement le nuget Identity du .Net Core est un peu différent de celui du .Net. En effet, le dernier venait avec plusieurs nuget sous forme de modules linguistiques, ce qui permettait de customiser les messages d’erreurs en provenance d’Identity selon le langage souhaité. Avec .Net Core ce n’est pas encore fait. Ce qu’on propose pour ce cas:

  • Ajoutez un fichier ressources avec des messages d’erreurs pour les langages souhaités autre que l’anglais, par exemple.

  • Créez une classe MultilanguageIdentityErrorDescriber héritant de IdentityErrorDescriber et qui va prendre en paramètre un IStringLocalizer<Ressource>.

  • Sur-définissez les membres de la classe mère pour récupérer les messages depuis la fichier ressource, exemple:

  • Configurez Identity pour qu’il utilise notre propre IdentityErrorDescriber sur la méthode Startup.ConfigureServices:

Conclusion

Dans ce billet nous avons vu qu’il est toujours possible d’utiliser Identity sans passer nécessairement par EF moyennant un peu d’effort bien sûr :wink:. A noter qu’à mesure que vos besoins grandissent (Authentification via Google, Facebook, Office 365…), vous serez amené à implémenter plus d’interfaces Identity pour que vous connectiez ce dernier avec vos référentiels de données et solutions de persistance spécifiques.

Ceci est mon deuxième billet de blog, j’ai toujours besoin de votre feed-back ==> N’oubliez pas de me faire une PR.


Badre BSAILA, Ingénieur d'étude et développement .NET sénior