Asp net core angular jwt authentication
Authentification de l’utilisateur avec Angular et ASP.NET Core
Publié le 5 mai 2017 • Mis à jour le 3 janvier 2022
MISE À JOUR : J’ai écrit une nouvelle version de cet article pour ASP.NET Core 2.0/Angular 5/Facebook OAuth que vous pouvez trouver ici. Si vous utilisez .NET Core 2.0, je vous suggère de vous y rendre car ce guide est basé sur ASP.NET Core 1.x et ne fonctionnera pas avec 2.0.
L’authentification de l’utilisateur est un élément fondamental de toute application significative. Malheureusement, sa mise en œuvre correcte peut être un exercice douloureux qui vole du temps et de l’énergie au détriment de fonctionnalités plus significatives de notre application. Dans cet article, nous allons apprendre étape par étape comment ajouter une fonctionnalité d’enregistrement et de connexion d’utilisateurs à une application Angular alimentée par une API backend ASP.NET Core. Notre approche utilisera des jetons Web JSON qui fournissent une méthode moderne et standard de l’industrie pour l’authentification basée sur des jetons dans le Web et les mobiles Apps.
Remarque , la partie Angular de ce guide est basée sur la version >= 2.x.
Environnement de développement
- Windows 10
- Visual Studio Code - v1.11.2
- Angular v4.0.2
- C# pour Visual Studio Code extension
- SQL Server Express 2017
- .NET Core SDK v1.0.1
- node v6.10.0
- npm v3.10.10
Obtenir le code
Dans mon dernier article, j’ai montré comment commencer avec Angular et ASP.NET Core, je vais donc utiliser le produit fini de cet article comme base pour celui-ci. Si vous suivez étape par étape, vous devrez récupérer ce code ici. Si vous voulez juste le code de cet article, c’est ici.
Soyez notifié sur les nouveaux posts
Directement de ma part, pas de spam, Pas de conneries. Contenu fréquent, utile, uniquement par e-mail.
Plan d’attaque
Voici un résumé des étapes que nous allons suivre dans ce tutoriel. La première moitié consiste à développer notre backend ASP.NET Core, tandis que la seconde moitié se concentre sur l’application frontale Angular.
Installer des packages
Nous allons utiliser le fournisseur d’identité principale ASP.NET avec SQL Server pour stocker les informations de notre compte d’utilisateur. Le fournisseur d’identité est un système d’adhésion qui nous permet d’ajouter facilement des fonctionnalités de connexion à notre application ASP.NET Core. Ajoutez les packages requis à partir de la ligne de commande dans le répertoire de votre projet.
Astuce : pour économiser sur la saisie de chacun d’entre eux, vous pouvez copier/coller la liste des paquets de mon .csproj ci-dessous dans le vôtre et exécuter
Voici à quoi ressemble la liste des paquets dans mon fichier csproj.
Remarque, la première fois que vous ouvrez le projet ou ajouter des packages à partir de la ligne de commande, vous pouvez être invité à les restaurer dans l’IDE, alors allez-y et faites-le lorsque vous le souhaitez.
Création d’un modèle de données
Nous avons besoin d’un objet pour représenter un utilisateur dans l’application. Heureusement, le fournisseur ASP.NET Core Identity dispose d’une classe intégrée que nous pouvons utiliser. Cela correspond à la table de base de données AspNetUsers et comporte quelques propriétés attendues telles que E-mail , Nom d’utilisateur , Date de naissance, etc. L’ajout de vos propres propriétés personnalisées est très simple. Nous pouvons simplement sous-classer IdentityUser et l’étendre avec les nouvelles propriétés dont nous avons besoin. J’aimerais que mon application puisse enregistrer le prénom et le nom d’un utilisateur, ce sont donc les 2 que nous allons ajouter. J’ai créé une nouvelle classe dans le dossier Models\Entities appelée AppUser.cs.
En plus de AppUser contenant les données d’identité de l’utilisateur principal, nous allons ajouter une autre classe pour représenter un rôle spécifique dans notre application au-delà d’un simple utilisateur générique : JobSeeker .
Cette classe a une référence à AppUser et est mappée à celle-ci via la propriété. Celle-ci est représentée sous la forme d’une clé étrangère dans la base de données. Je trouve que l’approche consistant à créer des classes/tables distinctes pour des rôles d’utilisateur uniques est meilleure que de polluer la table IdentityUser avec un tas de colonnes différentes alors qu’il y a des données spécifiques requises pour chacune. Si nous devions ajouter des rôles Admin ou Client, nous pourrions suivre le même modèle en stockant leurs données de profil universelles dans IdentityUser/AspNetUsers et en créant des classes/tables uniques pour des éléments plus spécifiques.
Création du contexte de base de données
DatabaseContext est la classe principale responsable de l’interaction avec les données en tant qu’objets dans Entity Framework Core. Il gère les objets d’entité pendant l’exécution, notamment Remplissage des objets avec des données de la base de données, suivi des modifications et conservation des données dans la base de données. Nous allons créer notre ApplicationDbContext en dérivant de IdentityDbContext, qui est essentiellement un DbContext standard qui inclut les ensembles d’entités liés à l’identité.
L’étape suivante consiste à inscrire ApplicationDbContext dans le conteneur afin de pouvoir facilement injecter une instance dans nos contrôleurs à l’avenir.
En Startup.cs j’ai ajouté ce qui suit à la méthode.
La chaîne de connexion est obtenue à partir du fichier appsettings.json.
Création de la base de données
Avec la configuration du modèle de données et du contexte, nous sommes prêts à créer la base de données réelle à l’aide de migrations de code d’abord.
Nous allons utiliser l’outil CLI Entity Framework pour générer et appliquer notre fichier de migration afin de créer une base de données SQL Server locale.
À partir de la ligne de commande à la racine du projet exécuté :
une fois la commande terminée, vous devez voir un nouveau dossier Migrations créé dans le projet.
En plus du fichier de migration, un instantané et un fichier Designer sont également créés. Ils sont utilisés pour stocker l’état actuel du modèle et sont mis à jour à chaque migration ultérieure. Ils aident EF à déterminer les modifications nécessaires pour que la base de données reste synchronisée avec le modèle.
La commande suivante applique la migration et crée la base de données.
Cela crée la base de données avec les tables liées à l’identité et une autre représentant l’entité JobSeeker.
Création de nouveaux utilisateurs
La responsabilité de la création de nouveaux utilisateurs appartiendra à une méthode d’action sur le AccountsController. Il accepte un RegistrationViewModel , effectue une validation sur celui-ci et appelle le Service UserManager pour créer le compte utilisateur dans la base de données.
Il convient également de mentionner l’utilisation d’AutoMapper ici pour mapper le RegistrationViewModel à un AppUser . Deuxièmement, une validation supplémentaire se produit implicitement sur le modèle de vue grâce à FluentValidation. Je ne vais pas entrer dans la configuration de ces deux pour le moment, mais si vous êtes intéressé, veuillez regarder de plus près le code ou me poser une question dans les commentaires. Si vous êtes nouveau dans ces bibliothèques, elles sont toutes deux idéales à avoir dans votre boîte à outils car elles peuvent vraiment aider à garder votre code propre et SEC.
À ce stade, j’ai testé le nouveau point de terminaison utilisateur en exécutant le projet et en utilisant Postman pour envoyer une demande et j’ai reçu une réponse de réussite de 200 OK - cool. La vérification de la base de données a également confirmé qu’un enregistrement avait été créé dans les tables AspNetUsers et JobSeekers.
Postman est un excellent outil pour effectuer rapidement des tests manuels de votre API. En envoyant à nouveau la même demande, j’obtiens une réponse d’erreur de la méthode car l’e-mail est déjà en cours d’utilisation. Nous allons voir comment gérer correctement les erreurs dans l’application Angular, mais pour l’instant, vous pouvez voir à quel point il est facile de piquer rapidement votre API et de vous assurer qu’elle répond comme prévu.
L’envoi d’un vide dans le corps de la requête déclenche le validateur de validation Fluent et entraîne une erreur.
La
dernière chose dont notre serveur ASP.NET Core a besoin est la possibilité d’autoriser les utilisateurs à utiliser des jetons Web JSON. En un mot, JWT est une méthode permettant à 2 parties de faire passer en toute sécurité des jetons contenant des propriétés connues sous le nom de revendications sur un sujet. Wikipédia a un résumé décent de cet usage. Il y a un peu plus à retenir, mais il suffit de commencer par une compréhension de base du concept.
Par exemple, un Le serveur peut générer un jeton portant la revendication « Connecté en tant qu’administrateur » et le fournir à un client. Le client peut ensuite utiliser ce jeton pour prouver qu’il est connecté en tant qu’administrateur.
Avant d’écrire du code pour prendre en charge JWT, nous devons nous assurer que les packages requis ont été ajoutés au projet ASP.NET Core, comme mentionné au début de l’article. Assurez-vous d’avoir ces 3 éléments dans votre fichier csproj :
- Microsoft.AspNetCore.Authentication.JwtBearer
- Microsoft.IdentityModel.Tokens
- System.IdentityModel.Tokens.Jwt
La première classe que nous allons ajouter est JwtIssuerOptions.cs qui fournira les propriétés de revendication JWT pour nos jetons générés.
Ensuite, j’ai ajouté une section de configuration associée à ma configuration appsettings.json.
La prochaine chose que nous allons faire est d’utiliser l’API de configuration intégrée pour lire les paramètres du fichier de configuration et les enregistrer avec conteneur IoC.
J’ai ajouté ce qui suit à la méthode ConfigureServices dans Startup.cs .
Nous devons ajouter du code supplémentaire en Startup.cs pour indiquer au middleware ASP.NET Core que nous voulons utiliser l’authentification JWT sur les requêtes entrantes. Dans la méthode Configure, j’ai ajouté le code suivant :
La dernière pièce majeure que j’ai ajoutée était la classe JwtFactory qui effectuera la tâche de création de jetons encodés.
La partie la plus intéressante de cette classe est la méthode GenerateEncodedToken qui crée un jeton encodé contenant les revendications que nous aimerions échanger entre le client et le backend. Notez que nous utilisons les JwtIssuerOptions que nous avons configurées à l’étape précédente en les injectant dans l’usine.
Authentification des utilisateurs et émission de jetons Web JSON
avec l’infrastructure JWT dans place, nous sommes prêts à le mettre en action à l’aide de l’AuthController. Il existe une seule action/route au niveau de /api/auth/login qui authentifiera les informations d’identification données à l’aide de l’API et, en cas de succès, renverra un nouveau jeton de sécurité à l’aide de qui peut être utilisé pour les demandes authentifiées ultérieures par l’utilisateur.
Une fois l’action créée, nous pouvons à nouveau utiliser postman pour tester notre point de terminaison d’authentification et voir à quoi ressemblent les choses.
J’ai utilisé le compte [email protected] que nous avons créé lors du test précédent et j’ai posté les informations d’identification dans /api/auth/login et succès ! - l’authentification a réussi et le serveur a renvoyé un jeton JWT.
Sécurisation d’un contrôleur à l’aide d’une autorisation basée sur les revendications
L’une des revendications que nous stockons dans le cadre de notre jeton n’est qu’une chaîne de caractères représentant un rôle nommé ApiAccess . Ceci est ajouté dans la méthode d’assistance GenerateClaimsIdentity.
Avec ce rôle stocké dans notre jeton, nous pouvons utiliser une vérification d’autorisation basée sur la revendication pour donner au rôle l’accès à certains contrôleurs et actions afin que seuls les utilisateurs possédant la revendication de rôle puissent accéder à ces ressources.
Tout ce que j’avais à faire pour activer la vérification des revendications était de l’enregistrer dans une stratégie dans la méthode ConfigureServices dans Startup.cs.
Une fois la stratégie en place, j’ai créé une nouvelle DashboardController.cs décorée avec ce qui signifie que seuls les utilisateurs disposant de la revendication de rôle ApiAccess dans le cadre de la politique ApiUser peuvent accéder à ce contrôleur.
J’ai testé une fois de plus avec postman en créant une requête GET au point de terminaison http://localhost:5000/api/dashboard/home, mais avant de l’envoyer, j’ai également inclus un en-tête de requête contenant le jeton JWT qui a été créé pour nous lors du précédent test d’authentification de l’utilisateur. La clé d’en-tête est Authorization et la valeur est au format Bearer xxx où xxx est le JWT. En regardant la réponse, j’obtiens un état de 200 OK et des données sécurisées dans le corps.
Nous avons testé le chemin heureux mais que se passe-t-il si une demande est faite avec un jeton invalide, manquant ou expiré ? Pour tester cela, j’ai changé un seul caractère dans le jeton, le rendant invalide, puis j’ai envoyé à nouveau la demande et j’ai reçu un code 401 non autorisé comme prévu - on dirait que JWT prétend que l’autorisation fonctionne !
Configuration de l’application Angular
Une fois le backend terminé, nous pouvons maintenant nous concentrer sur la création du frontend Angular pour voir comment l’authentification JWT fonctionne dans une application réelle. L’application a 3 fonctions principales :
Structurer l’application avec Les
modules existent depuis AngularJS 1.x et fournissent un mécanisme efficace pour regrouper les composants, directives et services associés, de manière à ce qu’ils puissent être combinés avec d’autres modules pour assembler une application. Dans ce projet, j’ai créé deux modules pour héberger les principales fonctionnalités.
À partir du dossier du projet src\app sur la ligne de commande, j’ai utilisé l’interface de ligne de commande pour générer des modules de compte et de tableau de bord.
Après l’exécution des commandes, de nouveaux dossiers représentant les modules sont ajoutés au projet. Nous allons câbler le code dans ceux-ci sous peu, mais nous avons quelques composants supplémentaires à ajouter en premier.
Création d’un composant de formulaire d’inscription
L’étape suivante consiste à créer un nouveau composant de formulaire afin que les utilisateurs puissent créer un nouveau compte. Pour ce faire, je suis retourné à la ligne de commande et j’ai exécuté ce qui suit à partir du dossier du module src\app\account.
Un nouveau dossier de formulaire d’inscription est généré avec les fichiers .ts, .scss et .html associés.
En créant des composants supplémentaires
, j’ai répété les étapes ci-dessus pour générer les composants restants dont nous aurons besoin, notamment :
- Le formulaire de connexion
- Un composant d’accueil représentant la vue par défaut de l’application
- Un composant spinner à afficher pendant que l’interface utilisateur est occupée
Communiquer avec le backend via UserService
Les fonctions clés d’enregistrement et d’authentification des utilisateurs sur le backend résident dans la classe UserService.
Notez que nous stockons le jeton d’autorisation émis par le serveur dans le stockage local des utilisateurs via l’appel. Nous verrons bientôt comment utiliser ce token pour faire des requêtes authentifiées à l’api backend.
En remplissant le formulaire d’inscription
Une fois ma composante et mon service en place, je Disposez des éléments nécessaires pour compléter la fonction d’enregistrement de l’utilisateur. Les principales étapes consistaient à ajouter le balisage de formulaire à registration-form.component.html et à ajouter le bouton d’envoi de liaison sur le formulaire à une méthode de la classe registration-form.component.ts.
La méthode est assez simple, elle appelle à transmettre les données de l’utilisateur et gère la réponse observable en conséquence. Si la validation côté serveur renvoie une erreur, celle-ci s’affiche pour l’utilisateur. Si la demande aboutit, l’utilisateur est acheminé vers la vue de connexion. L’indicateur de propriété déclenche le spinner afin que l’interface utilisateur puisse indiquer que l’application est occupée pendant que la demande est en cours.
Remplir le formulaire de connexion
Le formulaire de connexion est presque identique au formulaire d’inscription. J’ai ajouté le balisage requis à login-form.component.html et configuré une méthode de gestionnaire d’événements dans la classe login-form.component.ts.
le Le modèle ici est identique, appelez pour faire une requête au serveur avec les informations d’identification de l’utilisateur données et gérez la réponse en conséquence. Là encore, affichez les erreurs renvoyées par le serveur ou dirigez l’utilisateur vers le composant Tableau de bord s’il s’est authentifié avec succès.
À
l’heure actuelle, dans notre application, n’importe quel utilisateur peut naviguer n’importe où, changeons cela en limitant l’accès à certaines zones aux seuls utilisateurs connectés. Le routeur Angular fournit une fonctionnalité spécifique à cet effet dans les gardes de navigation.
Une garde est simplement une fonction ajoutée à votre configuration d’itinéraire qui renvoie soit .
signifie que la navigation peut se poursuivre. signifie que la navigation s’arrête et que l’itinéraire n’est pas accessible.
Les gardes sont enregistrés à l’aide de fournisseurs afin qu’ils puissent être injectés dans vos modules de routage de composants si nécessaire.
Dans cette application, j’ai créé auth.guard.ts pour protéger l’accès au tableau de bord, qui agit comme une fonctionnalité administrative que seuls les utilisateurs connectés peuvent voir.
L’AuthGuard est simplement une classe qui implémente . Il dispose d’une méthode unique qui vérifie l’état de connexion de l’utilisateur en appelant la méthode sur le UserService .
est un peu naïf car il vérifie simplement la présence du jeton JWT dans le stockage local et s’il existe, nous supposons que l’utilisateur est connecté en renvoyant . S’il n’est pas trouvé, l’utilisateur est redirigé vers la page de connexion.
Pour implémenter la garde dans le module de routage du tableau de bord, j’ai simplement importé et mis à jour la route racine avec une propriété de garde qui la référence.
La fonctionnalité est désormais protégée par le garde !
La
dernière chose que nous devons faire est de renvoyer notre JWT au serveur pour les appels d’API qui nécessitent une authentification. C’est là que nous serons en utilisant la politique d’autorisation que nous avons créée et appliquée précédemment au DashboardController dans notre API ASP.NET Core.
J’ai créé un nouveau service de tableau de bord avec une seule méthode qui récupère certaines données pour la page d’accueil en effectuant un appel HTTP authentifié au backend et en passant le jeton d’autorisation dans l’en-tête de la demande.
La méthode récupère simplement le from localStorage et l’inclut comme valeur de l’en-tête Authorization au format .
Une fois les demandes authentifiées en place, j’ai relancé le projet et j’ai pu effectuer un test de bout en bout en créant un nouvel utilisateur, en me connectant et en naviguant vers un itinéraire protégé dans le tableau de bord qui affichait un élément de données super sécurisé !
Pour conclure,
j’ai mentionné dans l’introduction que l’ajout de l’authentification dans la plupart des systèmes est souvent pénible et je pense que c’est le cas prouvé par la longueur de cet article, mais j’espère au moins vous avoir épargné du temps et des efforts en décrivant un plan clair des principales étapes nécessaires à la mise en œuvre de l’authentification basée sur les jetons avec Angular et ASP.NET Core.
La sécurité est une préoccupation sérieuse, c’est pourquoi ne prenez pas mot pour mot chaque exemple de code de ce tutoriel et copiez/collez dans des projets liés à la production sans un examen minutieux pour vous assurer que votre implémentation répond aux normes requises pour sécuriser votre application.
Si vous avez une question ou si vous construisez quelque chose de cool en utilisant Angular et ASP.NET Core, faites-le moi savoir dans les commentaires ci-dessous.
Merci d’avoir lu !
Recevez une notification sur les nouveaux messages
Directement de moi, pas de spam, pas de conneries. Contenu fréquent, utile, uniquement par e-mail.