image
image
image
image
image
image

Asp net core api token authentication

Authentification par jeton porteur dans ASP.NET Core

Il s’agit d’un article invité de Mike Rousos

Introduction

ASP.NET l’identité principaleIl est également simple de prendre en charge l’authentification par des fournisseurs externes à l’aide des packages d’authentification Google, Facebook ou Twitter ASP.NET Core. Un scénario d’authentification qui nécessite un peu plus de travail, cependant, consiste à s’authentifier via des jetons de porteur. J’ai récemment travaillé avec un client qui souhaitait utiliser les jetons porteurs JWT pour l’authentification dans les applications mobiles qui fonctionnaient avec un back-end ASP.NET Core. Étant donné que certains de leurs clients ne disposent pas de connexions Internet fiables, ils voulaient également pouvoir valider les jetons sans avoir à communiquer avec le serveur émetteur.

Dans cet article, je vous propose un aperçu rapide de la façon d’émettre des jetons porteurs JWT dans ASP.NET Core. Dans les articles suivants, je vais Montrer comment ces mêmes jetons peuvent être utilisés pour l’authentification et l’autorisation (même sans accès au serveur d’authentification ou au magasin de données d’identité).

Considérations relatives à la validation des jetons hors ligne

Tout d’abord, voici un schéma rapide de l’architecture souhaitée.

 

Le client dispose d’un serveur local avec des informations commerciales qui devront être consultées et mises à jour périodiquement par les appareils clients. Plutôt que de stocker les noms d’utilisateur et les mots de passe hachés localement, le client préfère utiliser un micro-service d’authentification commun hébergé dans Azure et utilisé dans de nombreux scénarios au-delà de celui-ci. Ce scénario particulier est cependant intéressant, car la connexion entre l’emplacement du client (où résident le serveur et les clients) et Internet n’est pas fiable. Par conséquent, ils aimeraient qu’un utilisateur puisse s’authentifier à un moment donné de la matinée lorsque le La connexion est active et dispose d’un jeton qui sera valide tout au long du quart de travail de cet utilisateur. Le serveur local doit donc être en mesure de valider le jeton sans accéder au service d’authentification Azure.

Cette validation locale est facilement réalisée avec les jetons JWT. Un jeton JWT contient généralement un corps contenant des informations sur l’utilisateur authentifié (identificateur de sujet, revendications, etc.), l’émetteur du jeton, l’audience (destinataire) à laquelle le jeton est destiné et une date d’expiration (après laquelle le jeton n’est pas valide). Le jeton contient également une signature cryptographique comme détaillé dans la RFC 7518. Cette signature est générée par une clé privée connue uniquement du serveur d’authentification, mais peut être validée par toute personne en possession de la clé publique correspondante. Un flux de travail de validation JWT (utilisé par AD et certains fournisseurs d’identité) consiste à demander la clé publique au serveur émetteur et à l’utiliser pour valider la signature du jeton. Dans notre scénario hors ligne, cependant, le serveur local peut être préparé à l’avance avec la clé publique nécessaire. Le défi de cette architecture est que le serveur local devra recevoir une clé publique mise à jour chaque fois que la clé privée utilisée par le service cloud change, mais cet inconvénient signifie qu’aucune connexion Internet n’est nécessaire au moment de la validation des jetons JWT.

Comme

mentionné précédemment, les bibliothèques Microsoft.AspNetCore.* ne prennent pas en charge l’émission de jetons JWT. Il existe cependant plusieurs autres bonnes options disponibles.

Tout d’abord, l’authentification Azure Active Directory fournit l’identité et l’authentification en tant que service. L’utilisation d’Azure AD est un moyen rapide d’obtenir une identité dans une application ASP.NET Core sans avoir à écrire de code de serveur d’authentification.

Sinon, si un développeur souhaite écrire l’authentification eux-mêmes, il existe quelques bibliothèques tierces disponibles pour gérer ce scénario. IdentityServer4 est un framework OpenID Connect flexible pour ASP.NET Core. Une autre bonne option est OpenIddict. Comme IdentityServer4, OpenIddict offre la fonctionnalité de serveur OpenID Connect pour ASP.NET Core. OpenIddict et IdentityServer4 fonctionnent bien avec ASP.NET Identity 3.

Pour cette démo, je vais utiliser OpenIddict. Il existe une excellente documentation sur l’accomplissement des mêmes tâches avec IdentityServer4 disponible dans la documentation d’IdentityServer4, que je vous encourage également à consulter.

Veuillez

noter que IdentityServer4 et OpenIddict sont actuellement des packages en pré-version . OpenIddict est actuellement publié en version bêta et IdentityServer4 en tant que RC, donc les deux sont toujours en développement et sujets à changement !

Configurer l’utilisateur Store

Dans ce scénario, nous allons utiliser un magasin d’utilisateurs ASP.NET basé sur Identity 3, accessible via Entity Framework Core. Étant donné qu’il s’agit d’un scénario courant, sa configuration est aussi simple que de créer une application web ASP.NET Core à partir de nouveaux modèles de projet et de sélectionner « comptes d’utilisateurs individuels » pour le mode d’authentification.

 

Ce modèle fournit un type par défaut et des connexions Entity Framework Core pour gérer les utilisateurs. La chaîne de connexion peut être modifiée pour pointer vers la base de données où vous souhaitez stocker ces données.

Étant donné que les jetons JWT peuvent encapsuler des revendications, il est intéressant d’inclure des revendications pour les utilisateurs autres que les valeurs par défaut du nom d’utilisateur ou de l’adresse e-mail. À des fins de démonstration, incluons deux types de revendications différents.

Adding Roles

ASP.NET Identity 3 inclut le concept de rôles. Pour en tirer parti, nous avons besoin de pour créer des rôles auxquels les utilisateurs peuvent être attribués. Dans une application réelle, cela se ferait probablement en gérant les rôles via une interface Web. Pour ce court exemple, cependant, je viens d’alimenter la base de données avec des rôles d’exemple en ajoutant ce code à :

// Initialiser certains rôles de test. Dans le monde réel, ceux-ci seraient configurés explicitement par un gestionnaire de rôleprivatestring[] roles = new[] { « Utilisateur », « Gestionnaire », « Administrateur » } ; privateasync Task InitializeRoles(RoleManager<IdentityRole> roleManager) { foreach (var role in roles) { if ( !await roleManager.RoleExistsAsync(role)) { var newRole = new IdentityRole(role) ; await roleManager.CreateAsync(newRole) ; // Dans le monde réel, il peut y avoir des revendications associées aux rôles// _roleManager.AddClaimAsync(newRole, new ) } } }

J’appelle ensuite à partir de la méthode de mon application. Le nécessaire en tant que paramètre peut être récupéré par IoC (il suffit d’ajouter un paramètre à votre méthode).

Étant donné que les rôles font déjà partie de ASP.NET Identity, il n’est pas nécessaire de modifier les modèles ou notre schéma de base de données.

Ajout de revendications personnalisées au modèle de données

Il est également possible d’encoder des revendications entièrement personnalisées dans des jetons JWT. Pour le démontrer, j’ai ajouté une propriété supplémentaire à mon type. À titre d’exemple, j’ai ajouté un entier appelé :

publicvirtualint OfficeNumber { get ; set ; }

Ce n’est pas quelque chose qui serait probablement une affirmation utile dans le monde réel, mais je l’ai ajouté dans mon échantillon spécifiquement parce que ce n’est pas le genre de revendication qui est déjà gérée par l’un des frameworks que nous utilisons.

J’ai également mis à jour les modèles de vue et les contrôleurs associés à la création d’un nouvel utilisateur pour permettre de spécifier le rôle et le numéro de bureau lors de la création de nouveaux utilisateurs.

J’ai ajouté les propriétés suivantes au type :

[Display(Name = "Is administrator")] publicbool IsAdministrator { get ; set ; } [display(Name = « Is manager »)] publicbool IsManager { get ; set ; } [Obligatoire] [display(Name = « Numéro de bureau »)] publicint OfficeNumber { get ; set ; }

J’ai également ajouté cshtml pour collecter ces informations à la vue d’enregistrement :

<divclass="form-group"> <labelasp-for="OfficeNumber"class="col-md-2 control-label"></label> <divclass="col-md-10"> <inputasp-for="OfficeNumber"class="form-control » /> <spanasp-validation-for="OfficeNumber"class="text-danger"></span> </div> </div> <divclass="form-group"> <labelasp-for="IsManager"class="col-md-2 control-label"></label> <divclass="col-md-10"> <inputasp-for="IsManager"class="form-control » /> <spanasp-validation-for="IsManager"class="text-danger"></span> </div> </div> <divclass="form-group"> <labelasp-for="IsAdministrator"class="col-md-2 control-label"></label> <divclass="col-md-10"> <inputasp-for="IsAdministrator"class="form-control » /> <spanasp-validation-for="IsAdministrator"class="text-danger"></span> </div> </div>

Enfin, j’ai mis à jour l’action pour définir les informations de rôle et de numéro de bureau lors de la création d’utilisateurs dans la base de données. Notez que nous ajoutons une revendication personnalisée pour le numéro de bureau. Cela permet de tirer parti du suivi personnalisé des réclamations de ASP.NET Identity. N’oubliez pas que ASP.NET Identity ne stocke pas les types de valeur de revendication, donc même dans les cas où la revendication est toujours un entier (comme dans cet exemple), elle sera stockée et renvoyée sous forme de chaîne. Plus loin dans cet article, j’expliquerai comment les revendications non liées à des chaînes peuvent être incluses dans les jetons JWT.

var user = new ApplicationUser { UserName = modèle. Email, Email = modèle. E-mail, OfficeNumber = modèle. Numéro de bureau } ; Résultat VAR = await _userManager.CreateAsync(utilisateur, modèle. Mot de passe) ; si (résultat. Réussi) { if (modèle. IsAdministrator) { await _userManager.AddToRoleAsync(user, « Administrator ») ; } elseif (modèle. IsManager) { await _userManager.AddToRoleAsync(user, « Manager ») ; } var officeClaim = new Claim(« office », utilisateur. OfficeNumber.ToString(), ClaimValueTypes.Integer) ; await _userManager.AddClaimAsync(utilisateur, officeClaim) ; ...

Mise à jour du schéma de base de données

Après avoir effectué ces modifications, nous pouvons utiliser les outils de migration d’Entity Framework pour mettre facilement à jour la base de données afin qu’elle corresponde (la seule modification apportée à la base de données doit être d’ajouter une colonne à la table users). Pour migrer, il suffit d’exécuter et à partir de la ligne de commande.

À ce stade, le serveur d’authentification doit permettre d’enregistrer de nouveaux utilisateurs. Si vous suivez le code, allez-y et ajoutez quelques exemples d’utilisateurs à ce stade.

Émission de jetons avec OpenIddict

Le paquet OpenIddict est encore en pré-version, il n’est donc pas encore disponible sur NuGet.org. Au lieu de cela, le paquet est disponible sur le flux MyGet aspnet-contrib.

Pour le restaurer, nous devons ajouter ce flux au fichier NuGet.config de notre solution. Si vous n’avez pas encore de fichier NuGet.config dans votre solution, vous pouvez en ajouter un qui ressemble à ceci :

< ?xml version="1.0 » encoding="utf-8 » ?> <configuration> <packageSources> <addkey="nuget.org"value="https://api.nuget.org/v3/index.json » /> <addkey="aspnet-contrib"value="https://www.myget.org/F/aspnet-contrib/api/v3/index.json » /> </packageSources> </configuration>

Une fois cela fait, ajoutez une référence à et dans la section des dépendances de votre fichier. OpenIddict.Mvc contient des extensions utiles qui permettent à OpenIddict de lier automatiquement les demandes OpenID Connect aux paramètres d’action MVC.

Il n’y a que quelques étapes nécessaires pour activer les points de terminaison OpenIddict.

Utiliser les types de modèles OpenIddict

La première modification consiste à mettre à jour votre type de modèle pour hériter de au lieu de .

Après avoir effectué cette modification, migrez également la base de données pour la mettre à jour ( et ).

Configurer OpenIddict

Ensuite, il est nécessaire d’enregistrer les types OpenIddict dans notre méthode dans notre type. Cela peut être fait avec un appel comme celui-ci :

services. AddOpenIddict<ApplicationDbContext>() . AddMvcBinders() . EnableTokenEndpoint(« /connect/token ») . UseJsonWebTokens() . AllowPasswordFlow() . AddSigningCertificate(jwtSigningCert) ;

Il est important de comprendre les méthodes spécifiques utilisées ici.

  • AddMvcBinders. Cette méthode enregistre des liants de modèle personnalisés qui rempliront les paramètres dans les actions MVC avec les demandes OpenID Connect lire à partir du contexte de la requête HTTP entrante. Ce n’est pas obligatoire, car les requêtes OpenID Connect peuvent être lues manuellement, mais c’est une commodité utile.
  • EnableTokenEndpoint. Cette méthode vous permet de spécifier le point de terminaison qui servira les jetons d’authentification. Le point de terminaison illustré ci-dessus (/connect/token) est un point de terminaison par défaut assez courant pour l’émission de jetons. OpenIddict a besoin de connaître l’emplacement de ce point de terminaison afin qu’il puisse être inclus dans les réponses lorsqu’un client demande des informations sur la façon de se connecter (à l’aide du point de terminaison .well-known/openid-configuration, qu’OpenIddict fournit automatiquement). OpenIddict validera également les demandes adressées à ce point de terminaison pour s’assurer qu’il s’agit bien de demandes OpenID Connect valides. Si une requête n’est pas valide (s’il ne contient pas de paramètres obligatoires comme , par exemple), OpenIddict rejettera la requête avant même qu’elle n’atteigne la Contrôleurs.
  • UseJsonWebTokens. Cela indique à OpenIddict d’utiliser JWT comme format pour les jetons de porteur qu’il produit.
  • AllowPasswordFlow. Cela active le type d’octroi de mot de passe lors de la connexion d’un utilisateur. Les différents flux d’autorisation OpenID Connect sont documentés dans les spécifications RFC et OpenID Connect. Le flux de mot de passe signifie que l’autorisation du client est effectuée en fonction des informations d’identification de l’utilisateur (nom et mot de passe) fournies par le client. Il s’agit du flux qui correspond le mieux à notre exemple de scénario.
  • AddSigningCertificate. Cette API spécifie le certificat qui doit être utilisé pour signer les jetons JWT. Dans mon exemple de code, je produis l’argument à partir d’un fichier pfx sur le disque (). Dans un scénario réel, le certificat serait plus susceptible d’être chargé à partir du magasin de certificats du serveur d’authentification, auquel cas une surcharge différente of serait utilisé (celui qui prend l’empreinte du certificat et le nom/emplacement du magasin).
    • Si vous avez besoin d’un certificat auto-signé à des fins de test, vous pouvez en produire un à l’aide des outils de ligne de commande et (qui doivent se trouver sur le chemin d’accès à l’invite de commande du développeur Visual Studio).
      • Cela créera un nouveau certificat de test auto-signé avec sa clé publique dans AuthSample.cer et sa clé privée dans AuthSample.pvk.
      • Cela combinera les fichiers pvk et cer en un seul fichier pfx contenant à la fois les clés publiques et privées du certificat (protégées par un mot de passe).
      • Ce fichier pfx est ce qui doit être chargé par OpenIddict (puisque la clé privée est nécessaire pour signer les jetons). Notez que cette clé privée (et tous les fichiers qui la contiennent) doivent être sécurisées.
  • DisableHttpsRequirement. Le code L’extrait ci-dessus n’inclut pas d’appel à , mais un tel appel peut être utile pendant les tests pour désactiver l’exigence selon laquelle les appels d’authentification doivent être effectués via HTTPS. Bien sûr, cela ne doit jamais être utilisé en dehors des tests, car cela permettrait d’observer les jetons d’authentification en transit et, par conséquent, de permettre à des parties malveillantes d’usurper l’identité d’utilisateurs légitimes.

Une

fois qu’il a été utilisé pour configurer les services OpenIddict, un appel à (qui doit venir après l’appel existant à ) doit être ajouté pour activer réellement OpenIddict dans le pipeline de traitement des requêtes HTTP de l’application.

Mise en œuvre du point de terminaison Connect/Token

La dernière étape nécessaire pour activer le serveur d’authentification consiste à implémenter le point de terminaison connect/token. L’appel effectué lors de la configuration d’OpenIddict indique où se trouvera le point de terminaison émetteur du jeton (et permet OpenIddict pour valider les demandes OIDC entrantes), mais le point de terminaison doit encore être implémenté.

Le propriétaire d’OpenIddict, Kévin Chalet, donne un bon exemple de la façon d’implémenter un point de terminaison de jeton prenant en charge un flux de mot de passe dans cet exemple. J’ai reformulé l’essentiel de la création d’un point de terminaison de jeton simple ici.

Tout d’abord, créez un nouveau contrôleur appelé et donnez-lui une action post. Bien sûr, les noms spécifiques ne sont pas importants, mais il est important que l’itinéraire corresponde à celui donné à EnableTokenEndpoint.

Donnez un paramètre à la méthode d’action. Comme nous utilisons le classeur MVC OpenIddict, ce paramètre sera fourni par OpenIddict. Alternativement (sans utiliser le liant de modèle OpenIddict), la méthode d’extension peut être utilisée pour récupérer la demande OpenID Connect.

En fonction du contenu de la demande, vous devez vérifier que la demande est valide.

  1. Vérifiez que le type d’octroi est conforme aux attentes (« Mot de passe » pour ce serveur d’authentification).
  2. Vérifiez que l’utilisateur demandé existe (à l’aide de la ASP.NET Identité ).
  3. Vérifiez que l’utilisateur demandé peut se connecter (puisque ASP.NET Identity autorise les comptes verrouillés ou non encore confirmés).
  4. Vérifiez que le mot de passe fourni est correct (encore une fois, à l’aide d’un ).

Si tout ce qui est dans la requête est vérifié, alors un peut être créé à l’aide de .

Les rôles et les revendications personnalisées connus de ASP.NET identité seront automatiquement présents dans le . Si des modifications sont nécessaires aux réclamations, elles peuvent être apportées dès maintenant.

Une série de mises à jour des réclamations qui sera importante consiste à joindre des destinations aux réclamations. Une revendication n’est incluse dans un jeton que si cette revendication inclut une destination pour ce type de jeton. Ainsi, même si le contiendra toutes les revendications d’identité ASP.NET, Ils ne seront inclus dans les jetons que s’ils ont des destinations appropriées. Cela permet à certaines revendications de rester privées et d’en inclure d’autres uniquement dans des types de jetons particuliers (jetons d’accès ou d’identité) ou si des portées particulières sont demandées. Pour les besoins de cette démo simple, j’inclus toutes les revendications pour tous les types de jetons.

C’est également l’occasion d’ajouter des revendications personnalisées supplémentaires au . En règle générale, le suivi des revendications avec ASP.NET Identity est suffisant, mais, comme mentionné précédemment, ASP.NET Identity ne se souvient pas des types de valeur de revendication. Donc, s’il était important que la revendication du bureau soit un entier (plutôt qu’une chaîne), nous pourrions l’ajouter ici en fonction des données de l’objet renvoyé par le . Les revendications ne peuvent pas être ajoutées directement à un, mais l’identité sous-jacente peut être récupérée et modifiée. Par exemple, si la revendication de l’office a été créée ici (au lieu de lors de l’enregistrement de l’utilisateur), elle pourrait être ajoutée comme suit :

var identity = (ClaimsIdentity)principal. Identité; var officeClaim = new Claim(« office », utilisateur. OfficeNumber.ToString(), ClaimValueTypes.Integer) ; officeClaim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken) ; identité. AddClaim(officeClaim) ;

Enfin, un peut être créé à partir du principal des revendications et utilisé pour connecter l’utilisateur. L’objet ticket nous permet d’utiliser des méthodes d’extension OpenID Connect utiles pour spécifier les étendues et les ressources auxquelles l’accès est accordé. Dans mon exemple, je passe les scopes demandés filtrés par ceux que le serveur est capable de fournir. Pour les ressources, je fournis une chaîne codée en dur indiquant la ressource à laquelle ce jeton doit accéder. Dans des scénarios plus complexes, les ressources demandées () peuvent être prises en compte lors de la détermination des revendications de ressource à inclure dans le ticket. Notez que les ressources (qui correspondent à l’élément d’audience d’un JWT) ne sont pas obligatoires selon la spécification JWT, bien que de nombreux consommateurs JWT s’y attendent.

Dans l’ensemble, voici une implémentation simple d’un point de terminaison connect/token :

[HttpPost] publicasync Task<IActionResult> Token(OpenIdConnectRequest request) { if ( !request. IsPasswordGrantType()) { // Renvoie une mauvaise requête si la demande n’est pas pour le type d’octroi de mot de passereturn BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = « Le type d’octroi spécifié n’est pas pris en charge. » }) ; } var user = await _userManager.FindByNameAsync(request. Nom d’utilisateur) ; if (user == null) { // Renvoie une mauvaise requête si l’utilisateur n’existe pasreturn BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = « Nom d’utilisateur ou mot de passe invalide » }) ; } // Vérifiez que l’utilisateur peut se connecter et qu’il n’est pas verrouillé.// Si l’authentification à deux facteurs est supporté, il serait également approprié de vérifier que 2FA est activé pour l’utilisateur if ( !await _signInManager.CanSignInAsync(user) || (_userManager.SupportsUserLockout & await _userManager.IsLockedOutAsync(user))) { // return bad request est que l’utilisateur ne peut pas se connecterreturn BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = « L’utilisateur spécifié ne peut pas se connecter. » }) ; } if ( !await _userManager.CheckPasswordAsync(utilisateur, demande. Password)) { // Renvoie une mauvaise requête si le mot de passe n’est pas validereturn BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = « Nom d’utilisateur ou mot de passe invalide » }) ; } // L’utilisateur est maintenant validé, donc réinitialiser le nombre de verrouillages, si nécessaireif (_userManager.SupportsUserLockout) { await _userManager.ResetAccessFailedCountAsync(user) ; } // Crée le principalvar principal = await _signInManager.CreateUserPrincipalAsync(utilisateur) ; Par défaut, les revendications ne seront pas associées à des destinations spécifiques, nous devons donc indiquer si elles doivent// être incluses ou non dans l’accès et l’identité tokens.foreach (var claim in principal. Claims) { // Pour cet exemple, il suffit d’inclure toutes les revendications dans tous les types de jetons.// En réalité, les destinations des revendications différeraient probablement selon le type de jeton et en fonction des portées demandées. claim. SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken) ; } // Créer un nouveau ticket d’authentification pour le principalvar ticket de l’utilisateur = new AuthenticationTicket( principal, new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme) ; Inclure les ressources et les portées, le cas échéantvar scope = new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }. Intersect(demande. GetScopes()) ; billet. SetResources(« http://localhost:5000/ ») ; billet. SetScopes(portée) ; Connectez-vous à l’utilisateurretour SignIn(ticket. Directeur, billet. Propriétés, billet. Schéma d’authentification) ; }

Test du serveur d’authentification

À ce stade, notre serveur d’authentification simple est terminé et devrait fonctionner pour émettre des jetons porteurs JWT pour les utilisateurs de notre base de données.

OpenIddict implémente OpenID Connect, de sorte que notre exemple doit prendre en charge un point de terminaison standard avec des informations sur la façon de s’authentifier auprès du serveur.

Si vous avez suivi la création de l’exemple, lancez l’application et accédez à ce point de terminaison. Vous devriez obtenir une réponse json similaire à ceci :

{ « issuer » : « http://localhost:5000/ », « jwks_uri » : « http://localhost:5000/.well-known/jwks », « token_endpoint » : « http://localhost:5000/connect/token », « code_challenge_methods_supported » : [ « S256 » ], « grant_types_supported » : [ « mot de passe » ], « subject_types_supported » : [ « public » ], « scopes_supported » : [ « openid », « profile », « email », « phone », « roles » ], « id_token_signing_alg_values_supported » : [ « RS256 » ] }

Cela donne aux clients des informations sur notre serveur d’authentification. Voici quelques-unes des valeurs intéressantes :

  • La propriété est le point de terminaison que les clients peuvent utiliser pour récupérer les clés publiques afin de valider les signatures de jeton de l’émetteur.
  • Indique le point de terminaison qui doit être utilisé pour les demandes d’authentification.
  • La propriété est une liste des types d’octroi pris en charge par le serveur. Dans le cas de cet échantillon, il s’agit uniquement de .
  • est une liste des étendues auxquelles un client peut demander l’accès.

Si vous souhaitez vérifier que le certificat est correct utilisé, vous pouvez accéder au point de terminaison pour voir les clés publiques utilisées par le serveur. La propriété de la réponse doit être l’empreinte numérique du certificat. Vous pouvez le vérifier par rapport à l’empreinte du certificat que vous prévoyez d’utiliser pour confirmer qu’ils sont identiques.

Enfin, nous pouvons tester le serveur d’authentification en tentant de vous connecter ! Cela se fait via un POST vers le . Vous pouvez utiliser un outil tel que Postman pour créer une demande de test. L’adresse de l’article doit être l’URI et le corps de l’article doit être codé x-www-form-urlencoded et inclure les éléments suivants :

  • grant_type doit être 'password' pour ce scénario.
  • Le nom d’utilisateur doit être le nom d’utilisateur pour se connecter.
  • Le mot de passe doit être le mot de passe de l’utilisateur.
  • scope doit être les étendues qui accèdent est souhaitée.
  • resource est un paramètre facultatif qui permet de spécifier la ressource à laquelle le jeton est censé accéder. L’utilisation de cette méthode peut vous aider à vous assurer qu’un jeton émis pour accéder à une ressource n’est pas réutilisé pour accéder à une autre.

Voici la requête complète et la réponse de ma part testant l’API connect/token :

Réponse de la demande

{ « token_type » : « Bearer », « access_token » : RBMzU5NDhGODhBQTg2NThFQkExMUZFOUIxMkI5Qzk5NjIiLCJ0eXAiOiJKV1QifQ. eyJ1bmlxdWVfbmFtZSI6Ik1pa2VAQ29udG9zby5jb20iLCJBc3BOZXQuSWRlbnRpdHkuU2VjdXJpdHlTdGFtcCI6ImMzM2U4NzQ5LTEyODAtNGQ5OS05OTMxLTI1Mzk1MzY3NDEzMiIsInJvbGUiOiJBZG1pbmlzdHJhdG9yIiwib2ZmaWNlIjoiMzAwIiwianRpIjoiY2UwOWVlMGUtNWQxMi00NmUyLWJhZGUtMjUyYTZhMGY3YTBlIiwidXNhZ2UiOiJhY2Nlc3NfdG9rZW4iLCJzY29wZSI6WyJlbWFpbCIsInByb2ZpbGUiLCJyb2xlcyJdLCJzdWIiOiJjMDM1YmU5OS0yMjQ3LTQ3NjktOWRjZC01NGJkYWRlZWY2MDEiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDEvIiwibmJmIjoxNDc2OTk3MDI5LCJleHAiOjE0NzY5OTg4MjksImlhdCI6MTQ3Njk5NzAyOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyJ9.q-c6Ld1b7c77td8B-0LcppUbL4a8JvObiru4FDQWrJ_DZ4_zKn6_0ud7BSijj4CV3d3tseEM-3WHgxjjz0e8aa4Axm55y4Utf6kkjGjuYyen7bl9TpeObnG81ied9NFJTy5HGYW4ysq4DkB2IEOFu4031rcQsUonM1chtz14mr3wWHohCi7NJY0APVPnCoc6ae4bivqxcYxbXlTN4p6bfBQhr71kZzP0AU_BlGHJ1N8k4GpijHVz2lT-2ahYaVSvaWtqjlqLfM_8uphNH3V7T7smaMpomQvA6u-CTZNJOZKalx99GNL4JwGk13MlikdaMFXhcPiamhnKtfQEsoNauA", « expires_in » : 1800 }

Le est le JWT et n’est rien de plus qu’une chaîne encodée en base64 en trois parties ([en-tête].[ corps]. [signature]). Un certain nombre de sites Web offrent une fonctionnalité de décodage JWT.

Le jeton d’accès ci-dessus a le contenu suivant :

{ « alg » : « RS256 », « kid » : « E57DAE4A35948F88AA8658EBA11FE9B12B9C9962 », « typ » : « JWT » }. { « unique_name » : « [email protected] », « AspNet.Identity.SecurityStamp » : « c33e8749-1280-4d99-9931-253953674132 », « role » : « Administrateur », « office » : « 300 », « jti » : « ce09ee0e-5d12-46e2-bade-252a6a0f7a0e », « usage » : « access_token », « scope » : [ « email », « profile », « roles » ], « sub » : « c035be99-2247-4769-9dcd-54bdadeef601 », « aud » : « http://localhost:5001/ », « nbf » : 1476997029, « exp » : 1476998829, « iat » : 1476997029, « iss » : « http://localhost:5000/ » }. [signature]

Les champs importants du jeton sont les suivants :

  • kid est l’ID de clé qui peut être utilisé pour rechercher la clé nécessaire à la validation de la signature du jeton.
    • x5t , de même, est l’empreinte du certificat de signature.
  • rôle et bureau capturer nos réclamations personnalisées.
  • exp est un horodatage indiquant le moment où le jeton doit expirer et ne plus être considéré comme valide.
  • ISS est l’adresse du serveur émetteur.

Ces champs peuvent être utilisés pour valider le jeton.

Conclusion et prochaines étapes

J’espère que cet article a fourni un aperçu utile de la façon dont les applications ASP.NET Core peuvent émettre des jetons de porteur JWT.

Recherchez un suivi de cet article à venir couvrant comment valider le jeton dans ASP.NET Core afin qu’il puisse être utilisé pour authentifier et connecter automatiquement un utilisateur. Et dans Si l’on s’en tient au scénario original que j’ai rencontré avec un client, nous nous assurerons que la validation peut être effectuée sans accès au serveur d’authentification ou à la base de données d’identité.

ressources