Authentification par jeton dans ASP.NET Core

Je travaille avec l’application ASP.NET Core. J’essaie d’implémenter l’authentification basée sur les jetons, mais je n’arrive pas à comprendre comment utiliser le nouveau système de sécurité pour mon cas. J’ai parcouru des exemples mais ils ne m’ont pas beaucoup aidé, ils utilisent l’authentification par cookie ou l’authentification externe (GitHub, Microsoft, Twitter).

Mon scénario est le suivant: l’application angularjs doit demander /token url en passant le nom d’utilisateur et le mot de passe. WebApi doit autoriser l’utilisateur et renvoyer access_token qui sera utilisé par angularjs dans les requêtes suivantes.

J’ai trouvé un excellent article sur la mise en œuvre exacte de ce dont j’avais besoin dans la version actuelle d’ASP.NET: l’ authentification basée sur les jetons utilisant ASP.NET Web API 2, Owin et Identity . Mais il n’est pas évident pour moi de faire la même chose dans ASP.NET Core.

Ma question est la suivante: comment configurer l’application ASP.NET Core WebApi pour qu’elle fonctionne avec une authentification basée sur des jetons?

    Mis à jour pour .Net Core 2:

    Les versions précédentes de cette réponse utilisaient RSA; ce n’est vraiment pas nécessaire si votre même code qui génère les jetons vérifie également les jetons. Cependant, si vous dissortingbuez la responsabilité, vous souhaitez probablement continuer à le faire en utilisant une instance de Microsoft.IdentityModel.Tokens.RsaSecurityKey .

    1. Créez quelques constantes que nous utiliserons plus tard. voici ce que j’ai fait:

       const ssortingng TokenAudience = "Myself"; const ssortingng TokenIssuer = "MyProject"; 
    2. Ajoutez ceci à ConfigureServices votre Startup.cs. Nous utiliserons l’dependency injection plus tard pour accéder à ces parameters. Je suppose que votre authenticationConfiguration est un object ConfigurationSection ou Configuration sorte que vous puissiez avoir une configuration différente pour le débogage et la production. Assurez-vous de stocker votre clé en toute sécurité! Cela peut être n’importe quelle chaîne.

       var keySecret = authenticationConfiguration["JwtSigningKey"]; var symmesortingcKey = new SymmesortingcSecurityKey(Encoding.UTF8.GetBytes(keySecret)); services.AddTransient(_ => new JwtSignInHandler(symmesortingcKey)); services.AddAuthentication(options => { // This causes the default authentication scheme to be JWT. // Without this, the Authorization header is not checked and // you'll get no results. However, this also means that if // you're already using cookies in your app, they won't be // checked by default. options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters.ValidateIssuerSigningKey = true; options.TokenValidationParameters.IssuerSigningKey = symmesortingcKey; options.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience; options.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer; }); 

      J’ai vu d’autres réponses modifier d’autres parameters, tels que ClockSkew ; les valeurs par défaut sont définies de manière à fonctionner pour les environnements dissortingbués dont les horloges ne sont pas exactement synchronisées. Ce sont les seuls parameters à modifier.

    3. Configurez l’authentification. Vous devriez avoir cette ligne avant tout middleware qui requirejs vos informations d’ User , telles que app.UseMvc() .

       app.UseAuthentication(); 

      Notez que cela ne provoquera pas l’émission de votre jeton avec SignInManager ou autre chose. Vous devrez fournir votre propre mécanisme pour sortir votre JWT – voir ci-dessous.

    4. Vous souhaiterez peut-être spécifier une AuthorizationPolicy . Cela vous permettra de spécifier des contrôleurs et des actions qui n’autorisent que les jetons Bearer comme authentification à l’aide de [Authorize("Bearer")] .

       services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType) .RequireAuthenticatedUser().Build()); }); 
    5. Voici la partie délicate: construire le jeton.

       class JwtSignInHandler { public const ssortingng TokenAudience = "Myself"; public const ssortingng TokenIssuer = "MyProject"; private readonly SymmesortingcSecurityKey key; public JwtSignInHandler(SymmesortingcSecurityKey symmesortingcKey) { this.key = symmesortingcKey; } public ssortingng BuildJwt(ClaimsPrincipal principal) { var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: TokenIssuer, audience: TokenAudience, claims: principal.Claims, expires: DateTime.Now.AddMinutes(20), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } } 

      Ensuite, dans votre contrôleur où vous voulez votre jeton, quelque chose comme:

       [HttpPost] public ssortingng AnonymousSignIn([FromServices] JwtSignInHandler tokenFactory) { var principal = new System.Security.Claims.ClaimsPrincipal(new[] { new System.Security.Claims.ClaimsIdentity(new[] { new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "Demo User") }) }); return tokenFactory.BuildJwt(principal); } 

      Ici, je suppose que vous avez déjà un principal. Si vous utilisez Identity, vous pouvez utiliser IUserClaimsPrincipalFactory<> pour transformer votre User en un ClaimsPrincipal .

    6. Pour le tester : Obtenez un jeton, mettez-le dans le formulaire jwt.io. Les instructions fournies ci-dessus vous permettent également d’utiliser le secret de votre configuration pour valider la signature!

    7. Si vous restituez ceci dans une vue partielle sur votre page HTML en combinaison avec l’authentification du porteur dans .Net 4.5, vous pouvez maintenant utiliser un ViewComponent pour faire la même chose. C’est la même chose que le code d’action du contrôleur ci-dessus.

    Travaillant à partir de la réponse fabuleuse de Matt Dekrey , j’ai créé un exemple pleinement fonctionnel d’authentification basée sur des jetons, fonctionnant contre ASP.NET Core (1.0.1). Vous pouvez trouver le code complet dans ce repository sur GitHub (twigs alternatives pour 1.0.0-rc1 , beta8 , beta7 ), mais en résumé, les étapes importantes sont les suivantes:

    Générer une clé pour votre application

    Dans mon exemple, je génère une clé aléatoire chaque fois que l’application démarre, vous devez en générer une et la stocker quelque part et la fournir à votre application. Voir ce fichier pour savoir comment je génère une clé aléatoire et comment vous pouvez l’importer à partir d’un fichier .json . Comme le suggèrent les commentaires de @kspearrin, l’ API de protection des données semble être un candidat idéal pour gérer les clés “correctement”, mais je n’ai pas encore réussi à le faire. S’il vous plaît soumettre une demande de tirage si vous y arrivez!

    Startup.cs – ConfigureServices

    Ici, nous devons charger une clé privée pour que nos jetons soient signés, ce qui nous permettra également de vérifier les jetons présentés. Nous stockons la clé dans une key variable de niveau classe que nous réutiliserons dans la méthode Configure ci-dessous. TokenAuthOptions est une classe simple qui contient l’identité de signature, l’audience et l’émetteur dont nous avons besoin dans TokenController pour créer nos clés.

     // Replace this with some sort of loading from config / file. RSAParameters keyParams = RSAKeyUtils.GetRandomKey(); // Create the key, and a set of token options to record signing credentials // using that key, along with the other parameters we will need in the // token controlller. key = new RsaSecurityKey(keyParams); tokenOptions = new TokenAuthOptions() { Audience = TokenAudience, Issuer = TokenIssuer, SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest) }; // Save the token options into an instance so they're accessible to the // controller. services.AddSingleton(tokenOptions); // Enable the use of an [Authorize("Bearer")] atsortingbute on methods and // classes to protect. services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​) .RequireAuthenticatedUser().Build()); }); 

    Nous avons également mis en place une politique d’autorisation pour nous permettre d’utiliser [Authorize("Bearer")] sur les noeuds finaux et les classes que nous souhaitons protéger.

    Startup.cs – Configurer

    Ici, nous devons configurer JwtBearerAuthentication:

     app.UseJwtBearerAuthentication(new JwtBearerOptions { TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = key, ValidAudience = tokenOptions.Audience, ValidIssuer = tokenOptions.Issuer, // When receiving a token, check that it is still valid. ValidateLifetime = true, // This defines the maximum allowable clock skew - ie // provides a tolerance on the token expiry time // when validating the lifetime. As we're creating the tokens // locally and validating them on the same machines which // should have synchronised time, this can be set to zero. // Where external tokens are used, some leeway here could be // useful. ClockSkew = TimeSpan.FromMinutes(0) } }); 

    TokenController

    Dans le contrôleur de jetons, vous devez avoir une méthode pour générer des clés signées à l’aide de la clé chargée dans Startup.cs. Nous avons enregistré une instance TokenAuthOptions dans Startup, nous devons donc l’injecter dans le constructeur de TokenController:

     [Route("api/[controller]")] public class TokenController : Controller { private readonly TokenAuthOptions tokenOptions; public TokenController(TokenAuthOptions tokenOptions) { this.tokenOptions = tokenOptions; } ... 

    Ensuite, vous devrez générer le jeton dans votre gestionnaire pour le sharepoint terminaison de connexion. Dans mon exemple, je prends un nom d’utilisateur et un mot de passe et validez ceux qui utilisent une instruction if. identité basée sur et générer le jeton pour cela:

     public class AuthRequest { public ssortingng username { get; set; } public ssortingng password { get; set; } } ///  /// Request a new token for a given username/password pair. ///  ///  ///  [HttpPost] public dynamic Post([FromBody] AuthRequest req) { // Obviously, at this point you need to validate the username and password against whatever system you wish. if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST")) { DateTime? expires = DateTime.UtcNow.AddMinutes(2); var token = GetToken(req.username, expires); return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires }; } return new { authenticated = false }; } private ssortingng GetToken(ssortingng user, DateTime? expires) { var handler = new JwtSecurityTokenHandler(); // Here, you should create or look up an identity for the user which is being authenticated. // For now, just creating a simple generic identity. ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) }); var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() { Issuer = tokenOptions.Issuer, Audience = tokenOptions.Audience, SigningCredentials = tokenOptions.SigningCredentials, Subject = identity, Expires = expires }); return handler.WriteToken(securityToken); } 

    Et ça devrait être ça. Ajoutez simplement [Authorize("Bearer")] à toute méthode ou classe que vous souhaitez protéger, et vous devriez obtenir une erreur si vous tentez d’y accéder sans un jeton présent. Si vous voulez renvoyer une erreur 401 au lieu d’une erreur 500, vous devez enregistrer un gestionnaire d’exceptions personnalisé comme dans mon exemple ici .

    Vous pouvez consulter les exemples de connexions OpenId qui illustrent comment gérer différents mécanismes d’authentification, y compris les jetons JWT:

    https://github.com/aspnet-consortingb/AspNet.Security.OpenIdConnect.Samples

    Si vous examinez le projet Cordova Backend, la configuration de l’API est comme suit:

      // Create a new branch where the registered middleware will be executed only for non API calls. app.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathSsortingng("/api")), branch => { // Insert a new cookies middleware in the pipeline to store // the user identity returned by the external identity provider. branch.UseCookieAuthentication(new CookieAuthenticationOptions { AutomaticAuthenticate = true, AutomaticChallenge = true, AuthenticationScheme = "ServerCookie", CookieName = CookieAuthenticationDefaults.CookiePrefix + "ServerCookie", ExpireTimeSpan = TimeSpan.FromMinutes(5), LoginPath = new PathSsortingng("/signin"), LogoutPath = new PathSsortingng("/signout") }); branch.UseGoogleAuthentication(new GoogleOptions { ClientId = "560027070069-37ldt4kfuohhu3m495hk2j4pjp92d382.apps.googleusercontent.com", ClientSecret = "n2Q-GEw9RQjzcRbU3qhfTj8f" }); branch.UseTwitterAuthentication(new TwitterOptions { ConsumerKey = "6XaCTaLbMqfj6ww3zvZ5g", ConsumerSecret = "Il2eFzGIrYhz6BWjYhVXBPQSfZuS4xoHpSSyD9PI" }); }); 

    La logique dans /Providers/AuthorizationProvider.cs et le RessourceController de ce projet méritent également d’être examinés;).

    Sinon, vous pouvez également utiliser le code suivant pour valider les jetons (il y a aussi un extrait pour le faire fonctionner avec signalR):

      // Add a new middleware validating access tokens. app.UseOAuthValidation(options => { // Automatic authentication must be enabled // for SignalR to receive the access token. options.AutomaticAuthenticate = true; options.Events = new OAuthValidationEvents { // Note: for SignalR connections, the default Authorization header does not work, // because the WebSockets JS API doesn't allow setting custom parameters. // To work around this limitation, the access token is resortingeved from the query ssortingng. OnResortingeveToken = context => { // Note: when the token is missing from the query ssortingng, // context.Token is null and the JWT bearer middleware will // automatically try to resortingeve it from the Authorization header. context.Token = context.Request.Query["access_token"]; return Task.FromResult(0); } }; }); 

    Pour émettre un jeton, vous pouvez utiliser les packages du serveur openId Connect comme suit:

      // Add a new middleware issuing access tokens. app.UseOpenIdConnectServer(options => { options.Provider = new AuthenticationProvider(); // Enable the authorization, logout, token and userinfo endpoints. //options.AuthorizationEndpointPath = "/connect/authorize"; //options.LogoutEndpointPath = "/connect/logout"; options.TokenEndpointPath = "/connect/token"; //options.UserinfoEndpointPath = "/connect/userinfo"; // Note: if you don't explicitly register a signing key, one is automatically generated and // persisted on the disk. If the key cannot be persisted, an exception is thrown. // // On production, using a X.509 certificatee stored in the machine store is recommended. // You can generate a self-signed certificatee using Pluralsight's self-cert utility: // https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.zip // // options.SigningCredentials.AddCertificate("7D2A741FE34CC2C7369237A5F2078988E17A6A75"); // // Alternatively, you can also store the certificatee as an embedded .pfx resource // directly in this assembly or in a file published alongside this project: // // options.SigningCredentials.AddCertificate( // assembly: typeof(Startup).GetTypeInfo().Assembly, // resource: "Nancy.Server.Certificate.pfx", // password: "Owin.Security.OpenIdConnect.Server"); // Note: see AuthorizationController.cs for more // information concerning ApplicationCanDisplayErrors. options.ApplicationCanDisplayErrors = true // in dev only ...; options.AllowInsecureHttp = true // in dev only...; }); 

    EDIT: J’ai implémenté une application à une seule page avec une implémentation d’authentification basée sur des jetons utilisant le framework frontal Aurelia et le kernel ASP.NET. Il existe également une connexion de signal R persistante. Cependant, je n’ai pas effectué d’implémentation de firebase database. Le code peut être vu ici: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth

    J’espère que cela t’aides,

    Meilleur,

    Alex

    Jetez un coup d’œil à OpenIddict – il s’agit d’un nouveau projet (au moment de la rédaction du présent document) qui facilite la configuration de la création de jetons JWT et le rafraîchissement des jetons dans ASP.NET 5. La validation des jetons est gérée par d’autres logiciels.

    En supposant que vous utilisez Identity avec Entity Framework , la dernière ligne correspond à ce que vous appendiez à votre méthode ConfigureServices :

     services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders() .AddOpenIddictCore(config => config.UseEntityFramework()); 

    Dans Configure , vous définissez OpenIddict pour servir les jetons JWT:

     app.UseOpenIddictCore(builder => { // tell openiddict you're wanting to use jwt tokens builder.Options.UseJwtTokens(); // NOTE: for dev consumption only! for live, this is not encouraged! builder.Options.AllowInsecureHttp = true; builder.Options.ApplicationCanDisplayErrors = true; }); 

    Vous configurez également la validation des jetons dans Configure :

     // use jwt bearer authentication app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.RequireHttpsMetadata = false; options.Audience = "http://localhost:58292/"; options.Authority = "http://localhost:58292/"; }); 

    Il y a une ou deux autres choses mineures, telles que votre DbContext doit dériver d’OpenIddictContext.

    Vous pouvez voir une explication complète sur cet article du blog: http://capesean.co.za/blog/asp-net-5-jwt-tokens/

    Une démo fonctionnelle est disponible à l’ adresse suivante : https://github.com/capesean/openiddict-test