Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JWT on .NET Core 2.0

I've been on quite an adventure to get JWT working on DotNet core 2.0 (now reaching final release today). There is a ton of documentation, but all the sample code seems to be using deprecated APIs and coming in fresh to Core, It's positively dizzying to figure out how exactly it's supposed to be implemented. I tried using Jose, but app. UseJwtBearerAuthentication has been deprecated, and there is no documentation on what to do next.

Does anyone have an open source project that uses dotnet core 2.0 that can simply parse a JWT from the authorization header and allow me to authorize requests for a HS256 encoded JWT token?

The class below doesn't throw any exceptions, but no requests are authorized, and I get no indication why they are unauthorized. The responses are empty 401's, so to me that indicates there was no exception, but that the secret isn't matching.

One odd thing is that my tokens are encrypted with the HS256 algorithm, but I see no indicator to tell it to force it to use that algorithm anywhere.

Here is the class I have so far:

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;         }     } } 
like image 316
Michael Draper Avatar asked Aug 15 '17 04:08

Michael Draper


People also ask

What is JWT in .NET core?

JSON Web Tokens (commonly known as JWT) is an open standard to pass data between client and server, and enables you to transmit data back and forth between the server and the consumers in a secure manner.


2 Answers

My tokenValidationParameters works when they look like this:

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

and

    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";     } 

Moreover, add options.RequireHttpsMetadata = false like this:

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

EDIT:

Dont forget to call

 app.UseAuthentication(); 

in Startup.cs -> Configure method before app.UseMvc();

like image 23
Adrian Księżarczyk Avatar answered Sep 21 '22 17:09

Adrian Księżarczyk


Here is a full working minimal sample with a controller. I hope you can check it using Postman or JavaScript call.

  1. appsettings.json, appsettings.Development.json. Add a section. Note, Key should be rather long and Issuer is an address of the service:

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

    !!! In real project, don't keep Key in appsettings.json file. It should be kept in Environment variable and take it like this:

    Environment.GetEnvironmentVariable("JWT_KEY"); 

UPDATE: Seeing how .net core settings work, you don't need to take it exactly from Environment. You may use setting. However,instead we may write this variable to environment variables in production, then our code will prefer environment variables instead of configuration.

  1. AuthRequest.cs : Dto keeping values for passing login and password:

    public class AuthRequest {     public string UserName { get; set; }     public string Password { get; set; } } 
  2. Startup.cs in Configure() method BEFORE app.UseMvc() :

    app.UseAuthentication(); 
  3. Startup.cs in 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. Add a controller:

        [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");         }} 

That's all folks! Cheers!

UPDATE: People ask how get Current User. Todo:

  1. In Startup.cs in ConfigureServices() add

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 
  2. In a controller add to constructor:

    private readonly int _currentUser; public MyController(IHttpContextAccessor httpContextAccessor) {    _currentUser = httpContextAccessor.CurrentUser(); } 
  3. Add somewhere an extension and use it in your Controller (using ....)

    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;     } } 
like image 153
alerya Avatar answered Sep 20 '22 17:09

alerya