JWT signifie et il s'agit d'un mécanisme d'autorisation et non d'authentification. Voyons donc quelle est la différence entre ces deux. JSON Web Token est le mécanisme qui permet de vérifier que l'utilisateur est exactement celui qu'il prétend être. Il s'agit d'un processus de connexion dans lequel un utilisateur fournit un nom d'utilisateur et un mot de passe, et le système les vérifie. L’authentification répond donc à la question : qui est l’utilisateur ? L'authentification est le mécanisme qui permet de vérifier les droits d'accès dont dispose l'utilisateur à une certaine ressource. Il s'agit d'un processus consistant à accorder aux utilisateurs certains rôles et un ensemble d'autorisations dont dispose un rôle particulier. Ainsi, l’autorisation répond à cette question : quels droits l’utilisateur a-t-il dans le système ? L'autorisation Il est important de comprendre que l’authentification vient toujours en premier et l’autorisation en second. En d’autres termes, vous ne pouvez pas obtenir d’autorisation avant d’avoir vérifié votre identité. Mais quelles sont les méthodes d’autorisation les plus populaires ? Il existe deux approches principales pour gérer l'autorisation pour l'application Web. Séances Une approche traditionnelle sur le Web pour les utilisateurs autorisés est une session côté serveur basée sur les cookies. Le processus démarre lorsqu'un utilisateur se connecte et qu'un serveur l'authentifie. Après cela, le serveur crée une session avec un ID de session et la stocke quelque part dans la mémoire du serveur. Le serveur renvoie l'ID de session au client et le client stocke l'ID de session dans des cookies. Pour chaque demande, le client envoie un ID de session dans le cadre de la demande, et le serveur vérifie l'ID de session dans sa mémoire et les autorisations de l'utilisateur liées à cette session. Jetons Une autre approche populaire consiste à utiliser des jetons pour l'autorisation. Le processus démarre de la même manière lorsqu'un utilisateur saisit son identifiant et ses mots de passe et qu'un client envoie une demande de connexion à un serveur. Au lieu de créer une session, le serveur génère un jeton signé avec le jeton secret. Ensuite, le serveur renvoie le jeton au client et celui-ci doit le stocker dans un stockage local. Semblable à l'approche basée sur la session, le client doit envoyer un jeton au serveur pour chaque requête. Cependant, le serveur ne stocke aucune information supplémentaire sur la session utilisateur. Le serveur doit valider que le token n'a pas changé depuis sa création et sa signature avec la clé secrète. Session vs jeton L'approche d'autorisation basée sur la session peut être vulnérable à une attaque connue sous le nom de Cross-Site Request Forgery (CSRF). Il s'agit d'une sorte d'attaque lorsque l'attaquant pointe vers un site auquel il est connecté pour effectuer des actions qu'il n'avait pas l'intention de faire, comme soumettre un paiement ou modifier un mot de passe. Une autre chose est que lorsque vous utilisez une approche d'autorisation basée sur la session, une session avec état est créée entre un client et un serveur. Le problème est que si un client souhaite accéder à différents serveurs dans le cadre de la même application, ces serveurs doivent partager un état de session. Dans un autre cas, il faudra que le client soit autorisé sur chaque serveur puisque la session va être différente. D'un autre côté, l'approche d'autorisation basée sur des jetons ne nécessite pas de stocker les données de session côté serveur et peut simplifier l'autorisation entre plusieurs serveurs. Cependant, les jetons peuvent toujours être volés par un attaquant et il peut également être difficile de les invalider. Nous verrons les détails et comment gérer l'invalidation plus loin dans cet article. JWT JSON Web Token (JWT) est un standard ouvert qui définit un moyen compact et autonome de transmettre en toute sécurité des informations entre les parties en tant qu'objet JSON. Ces informations peuvent être vérifiées et fiables car elles sont signées numériquement. Les JWT peuvent être signés à l'aide d'un secret (avec l'algorithme ) ou d'une paire de clés publique/privée à l'aide de ou . HMAC RSA ECDSA Structure JWT Les jetons Web JSON se composent de trois parties séparées par des points . Entête { "alg": "HS256", "typ": "JWT" } L'en-tête se compose généralement de deux parties : le type de jeton et l'algorithme de signature utilisé. Charge utile { "sub": "1234567890", "name": "John Doe", "admin": true } La charge utile contient les revendications, qui sont des déclarations sur l'utilisateur. La charge utile est ensuite codée pour former la deuxième partie du jeton Web JSON. Vous pouvez trouver une description des champs standard utilisés comme revendications . en Base64Url ici Signature HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) Pour créer la partie signature, vous devez prendre l'en-tête codé, la charge utile codée, un secret et l'algorithme spécifié dans l'en-tête et le signer. Le jeton ressemble généralement à ce qui suit : xxxxx.yyyyy.zzzzz Vous pouvez accéder à et déboguer un exemple de jeton ou le vôtre. Collez simplement votre jeton dans le champ et sélectionnez l' de signature du jeton. jwt.io Encodé algorithme projet .NET Maintenant que nous avons des connaissances théoriques sur le fonctionnement de JWT, nous pouvons les appliquer au projet réel. Supposons que nous disposions d'une API simple qui représente les opérations CRUD pour l'entité café. Nous allons créer un projet API ASP.NET Core qui représente l'API Coffee. Après cela, nous créerons un autre projet d'API ASP.NET Core qui représenterait une API d'identité pouvant générer JWT. Dans la vraie vie, vous utiliserez probablement ou ou à des fins d'authentification/autorisation. Cependant, nous créerions notre propre API d'identité pour montrer comment générer JWT. Lorsque l'API Identity est terminée, nous pouvons appeler son contrôleur et générer JWT en fonction des données de l'utilisateur. De plus, nous pouvons protéger l'API Coffee avec une configuration d'autorisation qui nécessite de transmettre JWT à chaque requête. Identity Server Okta Auth0 API de café Tout d’abord, nous allons créer un simple projet d’API ASP.NET Core qui représente l’API Coffee. Voici la structure de ce projet : Commençons par le dans le dossier . Il s’agit d’une entité simple avec des propriétés et . Coffee.cs Model Id Name namespace Hackernoon.Coffee.API.Model; public class Coffee { public int Id { get; set; } public string Name { get; set; } } Nous devons stocker nos entités tout en travaillant avec l'API. Alors, introduisons un simple stockage en mémoire. Il se trouve dans le fichier du dossier . Storage.cs Data namespace Hackernoon.Coffee.API.Data; public static class Storage { private static readonly List<Model.Coffee> Data = new(); public static List<Model.Coffee> GetAll() { return Data; } public static bool Create(Model.Coffee model) { if (Data.Any(c => c.Id == model.Id || c.Name == model.Name)) return false; Data.Add(new Model.Coffee { Id = model.Id, Name = model.Name }); return true; } public static bool Delete(int id) { if (Data.All(c => c.Id != id)) return false; Data.Remove(Storage.Data.First(c => c.Id == id)); return true; } public static bool Update(Model.Coffee model) { if (Data.All(c => c.Id != model.Id)) return false; Data.First(c => c.Id == model.Id).Name = model.Name; return true; } } Nous avons besoin d'une classe qui représenterait les requêtes adressées à l'API Coffee. Créons donc dans le dossier . CoffeeRequest.cs Contracts namespace Hackernoon.Coffee.API.Contracts; public class CoffeeRequest { public int Id { get; set; } public string Name { get; set; } } Une fois cela fait, nous pouvons implémenter dans le dossier qui représente les opérations CRUD pour l'entité coffee. CoffeeController.cs Controller using Hackernoon.Coffee.API.Contracts; using Hackernoon.Coffee.API.Data; using Microsoft.AspNetCore.Mvc; namespace Hackernoon.Coffee.API.Controllers; [Route("coffee")] [ApiController] public class CoffeeController : ControllerBase { [HttpGet] public IList<Model.Coffee> GetAll() { return Storage.GetAll(); } [HttpPost] public IActionResult Create([FromBody]CoffeeRequest request) { var model = new Model.Coffee { Id = request.Id, Name = request.Name }; if (!Storage.Create(model)) return new BadRequestResult(); return new OkResult(); } [HttpDelete] public IActionResult Delete(int id) { if (!Storage.Delete(id)) return new BadRequestResult(); return new OkResult(); } [HttpPut] public IActionResult Update([FromBody] CoffeeRequest request) { var model = new Model.Coffee() { Id = request.Id, Name = request.Name }; if (!Storage.Update(model)) return new BadRequestResult(); return new OkResult(); } } L'API Coffee est terminée et nous pouvons exécuter le projet et voir l'interface utilisateur de Swagger comme suit : API d'identité Créons un autre projet d'API ASP.NET Core qui représente l'API d'identité. Voici la structure de ce projet : Commençons par le dossier dans , qui représente la demande de génération d'un nouveau JWT avec les propriétés et . TokenGenerationRequest.cs Contracts Email Password namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } } Nous devons implémenter uniquement qui représente la logique de génération JWT. Mais avant de faire cela, le package NuGet doit être installé. TokenController.cs Microsoft.AspNetCore.Authentication.JwtBearer using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Hackernoon.Identity.API.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace Hackernoon.Identity.API.Controllers; [Route("token")] public class TokenController : ControllerBase { private const string SecretKey = "VerySecretAndLongKey-NeedMoreSymbolsHere-123"; private const string Issuer = "IdentityServerIssuer"; private const string Audience = "IdentityServerClient"; private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(20); [HttpPost] public string Create([FromBody]TokenGenerationRequest request) { var claims = new List<Claim> {new Claim(ClaimTypes.Email, request.Email) }; var jwt = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.Add(Lifetime), signingCredentials: CreateSigningCredentials()); return new JwtSecurityTokenHandler().WriteToken(jwt); } private static SigningCredentials CreateSigningCredentials() { return new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)), SecurityAlgorithms.HmacSha256); } } Notez que les const sensibles tels que , et doivent être placés quelque part dans la configuration. Ils sont codés en dur uniquement pour simplifier ce projet de test. Le champ est défini sur 20 minutes, ce qui signifie que le jeton sera valide pendant cette durée. Vous pouvez également configurer ce paramètre. SecretKey Issuer Audience Lifetime Nous pouvons maintenant exécuter le projet et voir l'interface utilisateur de Swagger comme suit : Appelons le point de terminaison et générons un nouveau JWT. Essayez la charge utile suivante : /token { "email": "john.doe@gmail.com", "password": "password" } L'API d'identité générera le JWT correspondant : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM Activation de l'autorisation dans l'API Coffee Désormais, lorsque l'API Identity est prête et nous fournit des jetons, nous pouvons garder l'API Coffee avec autorisation. Encore une fois, le package NuGet doit être installé. Microsoft.AspNetCore.Authentication.JwtBearer Nous devons enregistrer les services requis par les services d'authentification. Ajoutez le code suivant au fichier juste après avoir créé un générateur. Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "IdentityServerIssuer", ValidateAudience = true, ValidAudience = "IdentityServerClient", ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("VerySecretAndLongKey-NeedMoreSymbolsHere-123")), ValidateIssuerSigningKey = true, }; }); builder.Services.AddAuthorization(); Il est important de se rappeler que l’ordre dans les middlewares est important. Nous activons l'authentification en appelant la méthode et en spécifiant comme schéma d'authentification. C'est une constante qui contient une valeur . AddAuthentication() JwtBearerDefaults.AuthenticationScheme Bearer namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary>Default values used by bearer authentication.</summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } } Nous devons spécifier qui décrit quels paramètres de JWT seront validés lors de l'autorisation. Nous spécifions également similaire à l'API in Identity pour vérifier la signature JWT. Vérifiez plus de détails sur . TokenValidationParameters IssuerSigningKey signingCredentials TokenValidationParameters ici Le morceau de code suivant ajoute un middleware au générateur qui active les capacités d'authentification et d'autorisation. Il doit être ajouté entre les méthodes et . UseHttpsRedirection() MapControllers() app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); Maintenant, nous pouvons utiliser l'attribut sur le contrôleur ou ses actions. En appliquant ce code, toutes les actions dans sont désormais protégées par un mécanisme d'autorisation et JWT doit être envoyé dans le cadre de la demande. Authorize CoffeeController [Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { .. Si nous appelons n'importe quel point de terminaison de l'API Coffee, nous pouvons déboguer et voir qu'il est rempli et possède une avec les revendications que nous avons spécifiées dans JWT. C'est une chose importante pour comprendre comment ASP.NET Core gère l'autorisation sous le capot. HttpContext.User Identity Ajouter une autorisation à l'interface utilisateur Swagger Nous avons fait un excellent travail pour protéger l’API Coffee avec l’autorisation. Mais si vous exécutez le projet Coffee API et ouvrez l'interface utilisateur Swagger, vous ne pourrez pas envoyer JWT dans le cadre de la demande. Pour résoudre ce problème, nous devons mettre à jour le fichier avec le code suivant : Program.cs builder.Services.AddSwaggerGen(option => { option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); }); Après cela, nous pourrons voir le bouton dans le coin supérieur droit : Autoriser Lorsque vous cliquez sur le bouton , vous pourrez saisir JWT comme suit : Autoriser Utilisez Postman pour les tests Vous ne pouvez pas vous limiter à l'utilisation de l'interface utilisateur Swagger et pouvez effectuer des tests de l'API via l'outil Postman. Appelons d'abord le point de terminaison de l'API Identity. Nous devons spécifier l'en-tête avec la valeur dans la section puisque nous allons utiliser JSON comme charge utile. /token Content-Type application/json Headers Après cela, nous pouvons appeler le point de terminaison et obtenir un nouveau JWT. /token Maintenant, nous pouvons copier JWT et l'utiliser pour appeler l'API Coffee. Nous devons spécifier un en-tête similaire à l'API Identity si nous souhaitons tester, créer et mettre à jour les points de terminaison. L'en-tête doit également être défini avec la valeur . Après cela, appuyez simplement sur le bouton et voyez le résultat. Content-Type Authorization Bearer [your JWT value] Envoyer Autorisation basée sur les rôles Comme vous vous en souvenez, la partie charge utile de JWT est un ensemble de revendications dont les valeurs sont exactement des paires clé-valeur. L'autorisation basée sur les rôles permet de différencier l'accès aux ressources de l'application en fonction du rôle auquel appartient l'utilisateur. Si nous mettons à jour la méthode dans le fichier dans l'API Identity avec le code qui ajoute une nouvelle revendication pour le rôle ; nous pouvons gérer l'authentification basée sur les rôles dans l'API Coffee. est un nom prédéfini de la revendication de rôle. Create() TokenController.cs ClaimTypes.Role var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim(ClaimTypes.Role, "Barista") }; Mettez à jour l'attribut dans le fichier en spécifiant le nom du rôle : Authorize CoffeeController.cs [Authorize(Roles = "Barista")] Désormais, tous les utilisateurs qui appellent l’API Coffee doivent revendiquer le rôle avec la valeur . Sinon, ils obtiendront le code de statut . Barista 403 Forbidden Autorisation basée sur une revendication Un attribut peut facilement gérer l’authentification basée sur les rôles. Mais que se passe-t-il si cela ne suffit pas et que nous souhaitons différencier l'accès en fonction de certaines propriétés de l'utilisateur comme l'âge ou autre ? Vous avez probablement déjà deviné que vous pouvez ajouter vos revendications à JWT et les utiliser pour créer une logique d'autorisation. L'autorisation basée sur les rôles elle-même est un cas particulier d'autorisation basée sur les revendications, tout comme un rôle est le même objet de revendication d'un type prédéfini. Authorize Mettons à jour la méthode dans le fichier dans l'API Identity avec le code qui ajoute une nouvelle revendication . Create() TokenController.cs IsGourmet var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim("IsGourmet", "true") }; Dans le fichier Program.cs de l'API Coffee, nous devons créer une stratégie qui vérifie une réclamation et peut être utilisée dans l'attribut . Le code suivant doit être ajouté juste après l'appel de la méthode . Authorize AddAuthentication() builder.Services.AddAuthorization(opts => { opts.AddPolicy("OnlyForGourmet", policy => { policy.RequireClaim("IsGourmet", "true"); }); }); Mettez à jour l'attribut dans le fichier en spécifiant le nom de la stratégie : Authorize CoffeeController.cs [Authorize(Policy = "OnlyForGourmet")] Résumé Toutes nos félicitations! Vous avez fait un gros effort pour apprendre JWT dans .NET. Vous devez désormais avoir une solide compréhension des principes JWT et pourquoi il est important de l'utiliser pour effectuer l'autorisation dans les applications .NET. Mais nous n’avons fait qu’effleurer la surface dans le domaine de l’authentification et de l’autorisation dans les applications ASP.NET Core. Je suggère de consulter la documentation Microsoft concernant les sujets abordés dans cet article. Il existe également de nombreuses fonctionnalités intégrées pour la gestion des autorisations et des rôles dans la plate-forme .NET. Un bon ajout à cet article pourrait être Microsoft sur l'autorisation. la documentation