Asp.net Core 2.2 : identity et roles
Vous avez commencé à coder votre API, celle ci est déjà bien avancé et vous vous demander comment la sécuriser ? La sécu avant tout, mais après le reste Si on était pro (pro pro), on aurait commencé par la sécu. Mais on aime coder pour avoir du résultat. Et la sécu… bon, ça attendra… Oui mais non. En fait cela n’a pas plus beaucoup d’incidence aujourd’hui avec Asp.net Core car peut importe l’ordre d’implémentation, le framework est tellement mature qu’implémenter la sécurité en phase finale ou primaire n’a que peut d’incidence… enfin en théorie. Microsoft propose un « model » de sécurité pour la gestion des utilisateurs (authentification et autorisation) de votre API basé sur ce qu’il appelle « Identity » et souvent couplé à EntityFramework pour plus d’efficacité. Oui mais, qu’en est t-il si notre base de donnée n’est pas SQL Server et que notre couche d’accès aux données n’utilise pas EntityFramework ? Mise en situation Cet article tente d’explorer le « User Management » à la sauce Microsoft, avec une couche à nous custom. Partons du principe que nous avons déjà un modèle de données, une base de données Oracle par exemple, une couche DAL déjà toute prête (avec ou sans mapper type Dapper) et un service d’abstraction. Ce dernier va faire du CRUD (Create, Read, Update, Delete) et j’ajouterai List, un CRUDL quoi. Dedans on un « équivalent » utilisateur (user) que l’on nommera Operator, qui possède un login (username) et un mot de passe (hashé). Voyons voir comment coupler notre système avec les fonctionnalités de gestion utilisateur de base d’Asp.Net Core Identity. Sous le capot Afin d’implémenter ce modèle, on va devoir respecter ces interfaces : C’est une couche d’abstraction qui va permettre de s’affranchir de comment la couche de données gère ses utilisateurs. Du moment que ça passe par une classe qui implémente l’interface IUserStore et IRoleStore, le reste on s’en fou. (enfin à vous de gérer l’implémentation quand même…) UserManager et RoleManager comme leurs noms l’indique vont faire le boulot, on est coté couche métier avec une logique qui va vérifier par exemple la complexité du password (basé sur une configuration préétablit). Controler de test Créeons un controler « Access » basique avec 3 actions, toute en Get : Noter que dans un premier temps les attributs [Authorize] sont commentés. Maintenant fait un run de votre WebApi et requêter ces urls : https://localhost:44389/api/Access https://localhost:44389/api/Access/Authenticated https://localhost:44389/api/Access/RoleAdmin Vous devriez voir afficher respectivement : Vous êtes autorisé (anonyme). Vous êtes autorisé car vous êtes authentifié. Vous êtes autorisé car vous êtes authentifié et Admin. Bien entendu ceci n’est qu’un test temporaire pour valider que vos actions répondent bien. Maintenant vous pouvez décommenter les attributs [Authorize] Maintenant, nous devrions avoir un code de retour 200 pour le 1er get, comme avant, car le fait d’ajouter [AllowAnonymous] ne change rien, il ignore tout les attributs [Authorize], mais il n’y en a pas ici. (il est utile si vous encapsuler tout le controler avec un [Authorize] et qu’une seule action peut être anonyme, donc sans authorisation particulière.) Par contre pour l’action GetAuthenticated et GetAuthenticatedRoleAdmin nous devrions avoir un code de retour 401 : Unauthorized (les codes http), or si vous refaites une requête c’est le code 500 : internal server error qui s’affiche. Voici ce que l’API Asp.net Core nous retourne : InvalidOperationException : No authentication Scheme was specified, and there was no Default Challenge Scheme found. En effet, nous n’avons rien fait jusqu’ici pour dire à l’API comment nous voulions gérer nos authentifications. Ajout d’un schéma d’authentification Dans Startup.cs ajoutons ceci : Ici vous définissez une identité de type Operator avec des rôles de type Role. (on verra ça plus tard). Maintenant ré-exécuter les deux dernières requêtes, vous allez obtenir une 302 : Redirection temporaire, qui nous amène si on à taper l’url dans un navigateur vers une nouvelle page : https://localhost:44389/Account/Login?ReturnUrl=%2Fapi%2FAccess%2FAuthenticated Ce comportement est géré par Asp.net Core. L’attribut [Authorize] indique au framework que la ressource demandé est soumise à authentification. Donc au lieu de répondre un 401 unauthorized comme je l’indiquais, Asp.net Core redirige directement l’utilisateur vers un controler Account sur l’action Login. C’est utile si notre page login est sur le même serveur. Sinon faudra revoir ce comportement. Notez le paramètre ReturnUrl. Il va nous servir à re-rediriger l’utilisateur sur la page initialement demandé si besoin après un sign-in correct. Comment qu’on fait ? Tout d’abord, je vous conseille d’écrire qq tests avant de tout casser. Ils vont juste nous permettre de valider que nos modifications n’affecterons pas le comportement actuel. Pour utiliser le mode tout fait, dans le Startup.cs on ajoute le service suivant : Vous allez avoir besoin de Microsoft.Extensions.Identity.Core. Mais cette étape nous l’avons faite précédemment et on veut customiser la mécanique. C’est cette librairie qui va apporter les dépendances pour UserManager<T> et RoleManager<T>. UserManager fournit les APIs pour gérer l’utilisateur, comme le changement de mot de passe, la validation de mot de passe. Voici la liste de des dépendances nécessaire dans le constructor de UserManager : IUserStore<TUser> store: The persistence store the manager will operate over IOptions<IdentityOptions> optionsAccessor: The accessor used to access the IdentityOptions IPasswordHasher<TUser> passwordHasher: The password hashing implementation to use when saving passwords IEnumerable<IUserValidator<TUser>> userValidators: A collection of IUserValidator<TUser> to validate users against IEnumerable<IPasswordValidator<TUser>> passwordValidators: A collection of IPasswordValidator<TUser> to validate passwords against ILookupNormalizer keyNormalizer: The ILookupNormalizer to use when generating index keys for users IdentityErrorDescriber errors: The IdentityErrorDescriber used to provide error messages IServiceProvider services: The IServiceProvider used to resolve services ILogger<UserManager<TUser>> logger: The logger used to log messages, warnings and errors Ajoutons un Controler Account Le constructeur du controler prend en paramètre 4 classes qui viennent d’être instancier dans le Startup.cs. Ces dernières seront automatiquement injecté dans le controler à son instanciation. A partir de la vous n’avez plus qu’a coder des actions pour ce controler, la exemple HttpPost Login(string username, string password). Il faudra aussi implémenter les interfaces CustomUserStore : IUserStore et CustomRoleStore : IRoleStore qui seront couplé à votre service d’accès aux données. Personnellement, je ne suis pas allez plus loin, j’ai même fait marche arrière en se…