📜 ⬆️ ⬇️

Keeping up with the times: Using JWT in ASP.NET Core

The release of ASP.Net Core 1.0 was released in June 2016 and now, if you are not afraid of the age of the new framework, you can gently start microservice in production (they all use microservice architecture, aren't they?). In order to restrict access to your microservice for third parties, you need to do authentication using a fairly common method - tokens. In the article under the cat, we will tell you more about how to do this using JSON Web Token (JWT), as well as the pros and cons of this approach.



Typically, a token is a randomly generated string that is associated with a specific user and to retrieve its data (for example, id or email), it is necessary to make a query to the database (DB). But, what if we don’t need to make an extra query to the database with user data, but need to store them right inside the token? This is possible with JWT. Let us examine what JWT is and create a test project.

JWT is a signed JSON object containing something useful (for example, user id, user rights / roles), encoded in base64 and consisting of three parts separated by dots . : Header , Payload , Signature and usually looks like this aaaaaaa.bbbbbb.cccccc . More information can be found at jwt.io or RFC 7519 .
')

Project preparation


As usual, let's start by creating an empty project. After that, add the following dependencies to the project.json file:

 "Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0", "Microsoft.AspNetCore.Mvc.Core": "1.0.0", "Microsoft.AspNetCore.Mvc.Formatters.Json": "1.0.0" 

I use the Microsoft.AspNetCore.Mvc.Core assembly instead of Microsoft.AspNetCore.Mvc in order not to drag unnecessary (for our rest service) dependencies in the form of Razor , TagHelper , etc.

In ASP.NET Core, the starting configuration of the project is specified in the Startup.cs file, slightly correcting it:

 public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvcCore(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseMvc().UseMvcWithDefaultRoute(); } } 

Customize JWT


Open our Startup.cs and add the following:

 public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvcCore() .AddAuthorization(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { var key = Encoding.UTF8 .GetBytes("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"); var options = new JwtBearerOptions { TokenValidationParameters = { ValidIssuer = "ExampleIssuer", ValidAudience = "ExampleAudience", IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuerSigningKey = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero } }; app.UseJwtBearerAuthentication(options); app.UseMvcWithDefaultRoute(); } } 

In the ConfigureServices method, everything is pretty obvious, we add the use of the authorization service, and also register the HttpContextAccessor . For what we needed explicit registration HttpContextAccessor, we will find out a little later. Let us turn to the Configure method, in which the parameters are set up to validate the JWT token. Now, we are most interested in three parameters:

IssuerSigningKey is the key with which our token should be signed. For example, choose SymmetricSecurityKey , but you can also specify X509SecurityKey () or JsonWebKey if you have a lot of love for JSON.

ValidateIssuerSigningKey - we indicate that we will verify the key with which the JWT token was signed.
ValidateLifetime - set to true , because we want to control the lifetime of the token.

Create routes


Now we need to add two methods: one to generate a token, the second to verify JW authentication. Create a simple HomeController.cs controller:

  [Route("/")] public class HomeController { private readonly IHttpContextAccessor _context; public HomeController(IHttpContextAccessor context) { _context = context; } [HttpGet("token")] public dynamic GetToken() { var handler = new JwtSecurityTokenHandler(); var sec = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"; var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(sec)); var signingCredentials = new SigningCredentials(securityKey,SecurityAlgorithms.HmacSha256Signature); var identity = new ClaimsIdentity(new GenericIdentity("temp@jwt.ru"), new[] { new Claim("user_id", "57dc51a3389b30fed1b13f91") }); var token = handler.CreateJwtSecurityToken(subject: identity, signingCredentials: signingCredentials, audience: "ExampleAudience", issuer: "ExampleIssuer", expires: DateTime.UtcNow.AddSeconds(42)); return handler.WriteToken(token); } [Authorize, HttpGet("secure")] public dynamic Secret() { var currentUser = _context.HttpContext.User; return currentUser.Identity.Name; } } 

Since I use AspNetCore.Mvc.Core , the only way (although there may be another one) is to get to HttpContext - just through IHttpContextAccessor , which we registered earlier.

signingCredentials - we create a key with which we sign our token, it should be the same as the one we specified in Startup.cs when setting up JWT parameters.

identity - create our payload . Of course, in a real application we will get data from the repository, having checked them before, and now we will build some hardcode.

When creating a token, we specify its lifetime: expires: DateTime.UtcNow.AddSeconds(42) , rather trivial and flexible.

Run the application and execute the first request for a token:

 curl -X GET "http://localhost:<your_port>/token" 

In response, the token will return to us:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InRlc3RAdGVzdC5ydSIsInVzZXJfaWQiOiI1N2RjNTFhMzM4OWIzMGZlZDFiMTNmOTEiLCJuYmYiOjE0NzQyMTU4MDAsImV4cCI6MTQ3NDIxNTgzNSwiaWF0IjoxNDc0MjE1ODAwLCJpc3MiOiJFeGFtcGxlSXNzdWVyIiwiYXVkIjoiRXhhbXBsZUF1ZGllbmNlIn0.9NhOkoalaE70nIb-erH_waWx8rk6QJta5N19EiBLETQ 

Let's try to make a request for a secret route without a token:

 curl -X GET "http://localhost:<your_port>/secure" 

In response, 401 Unauthorized will be returned, as expected.

Now we will make a request with the received token:

 curl -X GET -H "Authorization: Bearer token_should_be_here" "http://localhost:<your_port>/secure" 

The response will contain an email, which we passed to the constructor when creating GenericIdentity .

If we wait 42 seconds, and our token lives so much, and we try to repeat the previous request, we will get: 401 Unauthorized and the WWW-Authenticate header will have the value: Bearer error="invalid_token", error_description="The token is expired" telling us about expired token.

Instead of conclusion


As with any solution, in addition to the pros, JWT also has disadvantages. For example, if we need to revoke a token, before its lifetime ends, then we can do it in two ways:


If you can allow the recall of all tokens at once in your project, then you can safely use JWT, otherwise the gain is not very big.

We made a prototype project using JWT, in which we can validate and create tokens. Of course, in a real project, everything will be a little different, because the format of the article is not enough to talk about all the JWT parameters in ASP.NET Core.

about the author



Slava Bobik is an engineer at Radario, an ASP.NET Core and OSS enthusiast. He is fond of distributed systems and skydiving.

Source: https://habr.com/ru/post/311594/


All Articles