85 votes

JWT sur .NET Core 2.0

J'ai vécu toute une aventure pour faire fonctionner JWT sur DotNet core 2.0 (qui atteint aujourd'hui sa version finale). Il existe un tonne La documentation est abondante, mais tous les exemples de code semblent utiliser des API obsolètes et venir de Core, ce qui donne le vertige lorsqu'il s'agit de savoir comment les mettre en œuvre. J'ai essayé d'utiliser Jose, mais app. UseJwtBearerAuthentication a été déprécié, et il n'y a aucune documentation sur ce qu'il faut faire ensuite.

Est-ce que quelqu'un a un projet open source qui utilise dotnet core 2.0 qui peut simplement analyser un JWT à partir de l'en-tête d'autorisation et me permettre d'autoriser les demandes pour un jeton JWT encodé HS256 ?

La classe ci-dessous ne lève aucune exception, mais aucune demande n'est autorisée et je n'obtiens aucune indication. pourquoi ils ne sont pas autorisés. Les réponses sont des 401's vides, donc pour moi cela indique qu'il n'y a pas eu d'exception, mais que le secret ne correspond pas.

Une chose étrange est que mes jetons sont chiffrés avec l'algorithme HS256, mais je ne vois aucun indicateur permettant de l'obliger à utiliser cet algorithme où que ce soit.

Voici la classe que j'ai jusqu'à présent :

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace Site.Authorization
{
    public static class SiteAuthorizationExtensions
    {
        public static IServiceCollection AddSiteAuthorization(this IServiceCollection services)
        {
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SECRET_KEY"));

            var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKeys = new List<SecurityKey>{ signingKey },

                // Validate the token expiry
                ValidateLifetime = true,
            };

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            })

            .AddJwtBearer(o =>
            {
                o.IncludeErrorDetails = true;
                o.TokenValidationParameters  = tokenValidationParameters;
                o.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();

                        c.Response.StatusCode = 401;
                        c.Response.ContentType = "text/plain";

                        return c.Response.WriteAsync(c.Exception.ToString());
                    }

                };
            });

            return services;
        }
    }
}

0 votes

J'ai désactivé la validation de la signature sans changement, toutes les demandes utilisent [Authorize] 401

2 votes

Pourriez-vous poster le code complet ? ou un projet de démonstration, j'aimerais voir comment vous avez réussi à le faire fonctionner...

1 votes

90voto

alerya Points 462

Voici un échantillon minimal fonctionnel complet avec un contrôleur. J'espère que vous pourrez le vérifier en utilisant Postman ou JavaScript call.

  1. appsettings.json, appsettings.Development.json. Ajoutez une section. Note, Key doit être assez long et Issuer est une adresse du service :

    ...
    ,"Tokens": {
        "Key": "Rather_very_long_key",
        "Issuer": "http://localhost:56268/"
    }
    ...

    ! !! Dans un projet réel, ne gardez pas la clé dans le fichier appsettings.json. Il faut la garder dans une variable d'environnement et la prendre comme ceci :

    Environment.GetEnvironmentVariable("JWT_KEY");

UPDATE : En voyant comment les paramètres de .net core fonctionnent, vous n'avez pas besoin de reprendre exactement l'environnement. Vous pouvez utiliser setting. Cependant, nous pouvons plutôt écrire cette variable dans les variables d'environnement en production, alors notre code préférera les variables d'environnement au lieu de la configuration.

  1. AuthRequest.cs : Dto gardant les valeurs pour passer le login et le mot de passe :

    public class AuthRequest
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
  2. Startup.cs dans la méthode Configure() AVANT app.UseMvc() :

    app.UseAuthentication();
  3. Startup.cs dans ConfigureServices() :

    services.AddAuthentication()
        .AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
    
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidIssuer = Configuration["Tokens:Issuer"],
                ValidAudience = Configuration["Tokens:Issuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
            };
    
        });
  4. Ajouter un contrôleur :

        [Route("api/[controller]")]
        public class TokenController : Controller
        {
            private readonly IConfiguration _config;
            private readonly IUserManager _userManager;
    
            public TokenController(IConfiguration configuration, IUserManager userManager)
            {
                _config = configuration;
                _userManager = userManager;
            }
    
            [HttpPost("")]
            [AllowAnonymous]
            public IActionResult Login([FromBody] AuthRequest authUserRequest)
            {
                var user = _userManager.FindByEmail(model.UserName);
    
                if (user != null)
                {
                    var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
                    if (checkPwd)
                    {
                        var claims = new[]
                        {
                            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                            new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
                        };
    
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
                        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
                        var token = new JwtSecurityToken(_config["Tokens:Issuer"],
                        _config["Tokens:Issuer"],
                        claims,
                        expires: DateTime.Now.AddMinutes(30),
                        signingCredentials: creds);
    
                        return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
                    }
                }
    
                return BadRequest("Could not create token");
            }}

C'est tout, les amis ! A la vôtre !

UPDATE : Les gens demandent comment obtenir l'utilisateur actuel. Todo :

  1. Dans Startup.cs dans ConfigureServices() ajouter

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
  2. Dans un contrôleur, ajouter au constructeur :

    private readonly int _currentUser;
    public MyController(IHttpContextAccessor httpContextAccessor)
    {
       _currentUser = httpContextAccessor.CurrentUser();
    }
  3. Ajouter quelque part une extension et l'utiliser dans votre contrôleur (en utilisant ....)

    public static class IHttpContextAccessorExtension
    {
        public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {
            var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
            int.TryParse(stringId ?? "0", out int userId);
    
            return userId;
        }
    }

1 votes

Cela a été très utile pour moi. La seule chose que je ne sais pas encore est comment vérifier le jeton pour les appels ultérieurs, ou comment déterminer qui est l'utilisateur actuellement connecté.

0 votes

C'est très facile. Dans la méthode du contrôleur, appelez : var currentUser = HttpContext.User.Identity.Name ; Cheers !

1 votes

Mec, cela a été d'une grande aide, merci beaucoup pour la réponse détaillée !

17voto

Mon tokenValidationParameters fonctionne quand ils ressemblent à ça :

 var tokenValidationParameters = new TokenValidationParameters
  {
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = GetSignInKey(),
      ValidateIssuer = true,
      ValidIssuer = GetIssuer(),
      ValidateAudience = true,
      ValidAudience = GetAudience(),
      ValidateLifetime = true,
      ClockSkew = TimeSpan.Zero
   };

y

    static private SymmetricSecurityKey GetSignInKey()
    {
        const string secretKey = "very_long_very_secret_secret";
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));

        return signingKey;
    }

    static private string GetIssuer()
    {
        return "issuer";
    }

    static private string GetAudience()
    {
        return "audience";
    }

De plus, ajoutez options.RequireHttpsMetadata = false comme ceci :

         .AddJwtBearer(options =>
       {         
           options.TokenValidationParameters =tokenValidationParameters         
           options.RequireHttpsMetadata = false;
       });

EDITAR :

N'oubliez pas d'appeler

 app.UseAuthentication();

dans Startup.cs -> Méthode de configuration avant app.UseMvc() ;

0 votes

Je parie que c'est l'appel à app.UseAuthentication() ; qui fera l'affaire, je ne savais pas que j'en avais besoin. Merci !

0 votes

Je pense que vous devez également spécifier ValidAudience, ValidIssuer et IssuerSigningKey. Cela n'a pas fonctionné sans cela pour moi

0 votes

Oui, c'est exactement ce que c'était. J'ai dû ajouter app.UseAuthentication() et c'est tout ce qu'il fallait. Merci beaucoup !

8voto

Abdul Hameed Points 364

Mise en œuvre de l'authentification JWT Bearer Token avec Web Api Demo pour Asp.net Core 2.0

Ajouter un paquet " Microsoft.AspNetCore.Authentication.JwtBearer "

Startup.cs ConfigureServices()

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(cfg =>
            {
                cfg.RequireHttpsMetadata = false;
                cfg.SaveToken = true;

                cfg.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = "me",
                    ValidAudience = "you",
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
                };

            });

Startup.cs Configurer()

// ===== Use Authentication ======
        app.UseAuthentication();

Utilisateur.cs // C'est une classe modèle juste pour l'exemple. Elle peut être n'importe quoi.

public class User
{
    public Int32 Id { get; set; }
    public string Username { get; set; }
    public string Country { get; set; }
    public string Password { get; set; }
}

UserContext.cs // C'est juste une classe de contexte. Elle peut être n'importe quoi.

public class UserContext : DbContext
{
    public UserContext(DbContextOptions<UserContext> options) : base(options)
    {
        this.Database.EnsureCreated();
    }

    public DbSet<User> Users { get; set; }
}

AccountController.cs

[Route("[controller]")]
public class AccountController : Controller
{

    private readonly UserContext _context;

    public AccountController(UserContext context)
    {
        _context = context;
    }

    [AllowAnonymous]
    [Route("api/token")]
    [HttpPost]
    public async Task<IActionResult> Token([FromBody]User user)
    {
        if (!ModelState.IsValid) return BadRequest("Token failed to generate");
        var userIdentified = _context.Users.FirstOrDefault(u => u.Username == user.Username);
            if (userIdentified == null)
            {
                return Unauthorized();
            }
            user = userIdentified;

        //Add Claims
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
            new Claim(JwtRegisteredClaimNames.Sub, "data"),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken("me",
            "you",
            claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: creds);

        return Ok(new
        {
            access_token = new JwtSecurityTokenHandler().WriteToken(token),
            expires_in = DateTime.Now.AddMinutes(30),
            token_type = "bearer"
        });
    }
}

UserController.cs

[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly UserContext _context;

    public UserController(UserContext context)
    {
        _context = context;
        if(_context.Users.Count() == 0 )
        {
            _context.Users.Add(new User { Id = 0, Username = "Abdul Hameed Abdul Sattar", Country = "Indian", Password = "123456" });
            _context.SaveChanges();
        }
    }

    [HttpGet("[action]")]
    public IEnumerable<User> GetList()
    {
        return _context.Users.ToList();
    }

    [HttpGet("[action]/{id}", Name = "GetUser")]
    public IActionResult GetById(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if(user == null)
        {
            return NotFound();
        }
        return new ObjectResult(user);
    }

    [HttpPost("[action]")]
    public IActionResult Create([FromBody] User user)
    {
        if(user == null)
        {
            return BadRequest();
        }

        _context.Users.Add(user);
        _context.SaveChanges();

        return CreatedAtRoute("GetUser", new { id = user.Id }, user);

    }

    [HttpPut("[action]/{id}")]
    public IActionResult Update(long id, [FromBody] User user)
    {
        if (user == null)
        {
            return BadRequest();
        }

        var userIdentified = _context.Users.FirstOrDefault(u => u.Id == id);
        if (userIdentified == null)
        {
            return NotFound();
        }

        userIdentified.Country = user.Country;
        userIdentified.Username = user.Username;

        _context.Users.Update(userIdentified);
        _context.SaveChanges();
        return new NoContentResult();
    }

    [HttpDelete("[action]/{id}")]
    public IActionResult Delete(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound();
        }

        _context.Users.Remove(user);
        _context.SaveChanges();

        return new NoContentResult();
    }
}

Test sur PostMan : You will receive token in response.

Passez TokenType et AccessToken dans Header dans d'autres webservices. enter image description here

Bonne chance ! Je ne suis qu'un débutant. J'ai seulement passé une semaine pour commencer à apprendre asp.net core.

0 votes

J'obtiens InvalidOperationException : Unable to resolve service for type 'WebApplication8.UserContext' while attempting to activate 'AccountController'. Lorsque j'essaie l'appel postman à Post to account/api/token

7voto

Long Field Points 26

Voici une solution pour vous.

Dans votre startup.cs, tout d'abord, configurez-le comme services :

  services.AddAuthentication().AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                IssuerSigningKey = "somethong",
                ValidAudience = "something",
                :
            };
        });

Ensuite, appelez ce service dans la configuration

          app.UseAuthentication();

maintenant vous pouvez l'utiliser dans votre contrôleur en ajoutant l'attribut

          [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
          [HttpGet]
          public IActionResult GetUserInfo()
          {

Pour des détails complets sur le code source qui utilise angular comme Frond-end, voir aquí

0 votes

C'est la réponse qui a sauvé mon bacon ! Ce serait bien de pouvoir simplement utiliser [Authorize]. J'imagine que cela peut être géré dans le fichier Startup.cs.

1 votes

Simon, parce que vous pouvez avoir plus d'un schéma dans la même application asp.net core mvc, comme services.AddAuthentication().AddCookie().AddJwtBearer() ;

1 votes

Vous pouvez également définir un schéma d'authentification par défaut au moyen de l'option services.AddAuthorization au démarrage.

4voto

pcdev Points 1427

Voici mon implémentation pour une API .Net Core 2.0 :

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(
        config =>
        {
            // This enables the AuthorizeFilter on all endpoints
            var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
            config.Filters.Add(new AuthorizeFilter(policy));

        }
        ).AddJsonOptions(opt =>
        {
            opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });

        services.AddLogging();

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Audience = Configuration["AzureAD:Audience"];  
            options.Authority = Configuration["AzureAD:AADInstance"] + Configuration["AzureAD:TenantId"];
        });            
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication(); // THIS METHOD MUST COME BEFORE UseMvc...() !!
        app.UseMvcWithDefaultRoute();            
    }

appsettings.json :

{
  "AzureAD": {
    "AADInstance": "https://login.microsoftonline.com/",
    "Audience": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Domain": "mydomain.com",
    "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  ...
}

Le code ci-dessus active l'authentification sur tous les contrôleurs. Pour permettre un accès anonyme, vous pouvez décorer un contrôleur entier :

[Route("api/[controller]")]
[AllowAnonymous]
public class AnonymousController : Controller
{
    ...
}

ou simplement décorer une méthode pour autoriser un seul point de terminaison :

    [AllowAnonymous]
    [HttpPost("anonymousmethod")]
    public async Task<IActionResult> MyAnonymousMethod()
    {
        ...
    }

Notes :

  • C'est ma première tentative d'authentification AD - si quelque chose ne va pas, faites-le moi savoir !

  • Audience doit correspondre au ID de la ressource demandé par le client. Dans notre cas, notre client (une application Web Angular) a été enregistré séparément dans Azure AD et a utilisé son Id client, que nous avons enregistré comme Audience dans l'API.

  • ClientId s'appelle ID de l'application dans le portail Azure (pourquoi ? ?), l'ID de l'application qui s'enregistre pour l'API.

  • TenantId s'appelle ID du répertoire dans le portail Azure (pourquoi ? ?), sous la rubrique Azure Active Directory > Propriétés

  • Si vous déployez l'API en tant que Web App hébergée par Azure, assurez-vous de définir les paramètres de l'application :

    eg. AzureAD:Audience / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X