Implementation of JWT authentication in ASP. Net core3.0

Time:2020-3-16

Brief introduction of JWT certification

There are a lot of JWT introductions on the Internet, which are not covered here. Let’s take a look at the structure of JWT.

JWT is mainly composed of three parts, as follows:


HEADER.PAYLOAD.SIGNATURE

HEADERMetadata including token, mainly encryption algorithm, and signature type, as shown in the following information

The object type of encryption is JWT, and the encryption algorithm is HMAC SHA-256


{"alg":"HS256","typ":"JWT"}

Then it needs to be encoded by Base64 and stored in the token


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9  

PayloadIt mainly contains some declaration information (claim), which is the data structure of key value pair.

Usually such as user name, role and other information, expiration date, etc., because it is not encrypted, it is not recommended to store sensitive information.


{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"admin","exp":1578645536,"iss":"webapi.cn","aud":"WebApi"}

It also needs to be stored in the token after being encoded by Base64


eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9

SignatureJWT is required to generate a final signature according to JWs (JSON web signature). Add the encoded header and payload information together, and then use a strong encryption algorithm, such as hmacha256, to encrypt. HS256(BASE64(Header).Base64(Payload),secret)


2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4

The final token generated is as follows


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4

development environment

Framework: ASP. Net 3.1

IDE:VS2019

Using JWT authentication in asp.net 3.1 web API

Execute the following command on the command line to create a webpix project:


dotnet new webapi -n Webapi -o WebApi

In particular, 3. X does not have the microsoft.aspnetcore.authentication.jwtbearer Library of JWT by default, so you need to manually add nuget package, switch to the directory where the project is located, and execute the. Net cli command


dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 3.1.0

Create a simple poco class to store the information used to sign off or verify JWT


using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Webapi.Models

{
  public class TokenManagement
  {
    [JsonProperty("secret")]
    public string Secret { get; set; }

    [JsonProperty("issuer")]
    public string Issuer { get; set; }

    [JsonProperty("audience")]
    public string Audience { get; set; }

    [JsonProperty("accessExpiration")]
    public int AccessExpiration { get; set; }

    [JsonProperty("refreshExpiration")]
    public int RefreshExpiration { get; set; }
  }
}

Then inappsettings.Development.jsonIncrease the configuration information used by JWT (if the build environment isappsettings.jsonJust add)


"tokenManagement": {
    "secret": "123456",
    "issuer": "webapi.cn",
    "audience": "WebApi",
    "accessExpiration": 30,
    "refreshExpiration": 60
  }

Then add the read configuration information in the configureservices method of the startup class


public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllers();
      services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
      var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();

    }

So far, we have completed some basic work. Next, inject the authentication service of JWT into webapi, and enable the authentication Middleware in the middleware pipeline.

Namespace of JWT validation service to be referenced in startup class


using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

Then inConfigureServicesAdd the following logic to the method


services.AddAuthentication(x =>
      {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
      }).AddJwtBearer(x =>
      {
        x.RequireHttpsMetadata = false;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
          ValidateIssuerSigningKey = true,
          IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
          ValidIssuer = token.Issuer,
          ValidAudience = token.Audience,
          ValidateIssuer = false,
          ValidateAudience = false
        };
      });

againConfigureEnable validation in method


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }

      app.UseHttpsRedirection();

      app.UseAuthentication();
      app.UseRouting();

      app.UseAuthorization();

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllers();
      });
    }

The above completes the verification function of JWT, and the following needs to add the logic of signing token. We need to add a special controller for user authentication and token issuance, namedAuthenticationControllerAdd a dto class of the request


public class LoginRequestDTO
  {
    [Required]
    [JsonProperty("username")]
    public string Username { get; set; }


    [Required]
    [JsonProperty("password")]
    public string Password { get; set; }
  }

[Route("api/[controller]")]
  [ApiController]
  public class AuthenticationController : ControllerBase
  {
    [AllowAnonymous]
     [HttpPost, Route("requestToken")]
    public ActionResult RequestToken([FromBody] LoginRequestDTO request)
    {
      if (!ModelState.IsValid)
      {
        return BadRequest("Invalid Request");
      }

      return Ok();

    }
  }

At present, the above controller only implements the basic logic. Next, we need to create a token issuing service to complete the specific business. The first step is to create a corresponding service interface namedIAuthenticateService


public interface IAuthenticateService
  {
    bool IsAuthenticated(LoginRequestDTO request, out string token);
  }

Next, implement the interface


public class TokenAuthenticationService : IAuthenticateService
  {
    public bool IsAuthenticated(LoginRequestDTO request, out string token)
    {
      throw new NotImplementedException();
    }
  }

stayStartupOfConfigureServicesMethod to register a service


services.AddScoped<IAuthenticateService, TokenAuthenticationService>();

Inject iauthenticatservice service into controller and improve action


public class AuthenticationController : ControllerBase
  {
    private readonly IAuthenticateService _authService;
    public AuthenticationController(IAuthenticateService authService)
    {
      this._authService = authService;
    }
    [AllowAnonymous]
     [HttpPost, Route("requestToken")]
    public ActionResult RequestToken([FromBody] LoginRequestDTO request)
    {
      if (!ModelState.IsValid)
      {
        return BadRequest("Invalid Request");
      }

      string token;
      if (_authService.IsAuthenticated(request, out token))
      {
        return Ok(token);
      }

      return BadRequest("Invalid Request");

    }
  }

Under normal circumstances, we will verify whether the user is legal according to the requested user and password. We need to connect to the database to obtain data for verification. For convenience, we assume that any requested user is legal.

Here is a separate user managed service. Instead of adding the corresponding logic to iauthenticateservice, it mainly follows thePrinciple of single responsibility。 First, as above, create a service interfaceIUserService


public interface IUserService
  {
    bool IsValid(LoginRequestDTO req);
  }

RealizationIUserServiceInterface

public class UserService : IUserService
  {
    //Simulation test, which is validated by human by default
    public bool IsValid(LoginRequestDTO req)
    {
      return true;
    }
  }

Also register in container


services.AddScoped<IUserService, UserService>();

Next, we need to improve the logic of tokenauthenticationservice issuing token. First, we need to inject iuserservice and token management, and then implement the specific business logic. The generation of this token is still based on the API provided by JWT class library, which is not described in detail.

Note in particular that tokenmanagement is injected by ioptions interface type. Remember in startup? We register tokenmanagement type through configuration item.


public class TokenAuthenticationService : IAuthenticateService
  {
    private readonly IUserService _userService;
    private readonly TokenManagement _tokenManagement;
    public TokenAuthenticationService(IUserService userService, IOptions<TokenManagement> tokenManagement)
    {
      _userService = userService;
      _tokenManagement = tokenManagement.Value;
    }
    public bool IsAuthenticated(LoginRequestDTO request, out string token)
    {
      token = string.Empty;
      if (!_userService.IsValid(request))
        return false;
      var claims = new[]
      {
        new Claim(ClaimTypes.Name,request.Username)
      };
      var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
      var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
      var jwtToken = new JwtSecurityToken(_tokenManagement.Issuer, _tokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration), signingCredentials: credentials);

      token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

      return true;

    }
  }

Be ready to test the API for trial use, and mark the authorize feature to indicate that authorization is required!


[ApiController]
  [Route("[controller]")]
  [Authorize]
  public class WeatherForecastController : ControllerBase
  {
    private static readonly string[] Summaries = new[]
    {
      "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
      _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
      var rng = new Random();
      return Enumerable.Range(1, 5).Select(index => new WeatherForecast
      {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = rng.Next(-20, 55),
        Summary = Summaries[rng.Next(Summaries.Length)]
      })
      .ToArray();
    }
  }

Support we can test and verify, we can use postman to make HTTP request, start HTTP service first, get URL, test an interface that needs authorization first, but does not carry token information, the return is 401, indicating unauthorized

Next, we get the token through the authentication interface and report an error. After querying, we find that the secret key length of hs256 algorithm is 128 bits, which is converted to at least 16 characters. The previously set secret key is 123456, which causes an exception.


System.ArgumentOutOfRangeException: IDX10603: Decryption failed. Keys tried: 'HS256'. Exceptions caught: '128'. token: '48' (Parameter 'KeySize') at

Update secret key


 "tokenManagement": {
    "secret": "123456123456123456",
    "issuer": "webapi.cn",
    "audience": "WebApi",
    "accessExpiration": 30,
    "refreshExpiration": 60
  }

Reissue the request and obtain the token successfully

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDUyMDMsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.AehD8WTAnEtklof2OJsvg0U4_o8_SjdxmwUjzAiuI-o

Take the token to the previously requested API, retest, and obtain the data successfully

summary

Token based authentication makes it easier to build a distributed / loosely coupled system. The token generated anywhere can be used for signature verification only if it has the same secret key.

Of course, we need to make good use of JWT authentication mode, and other security details need to be dealt with, such as sensitive information can’t be stored in payload, encrypted transmission mode using HTTPS, etc., which can be further secured according to the actual needs of the business!

At the same time, we also found that using token can get rid of the limitation of cookies, so JWT is the first choice for mobile app development!

The above is the whole content of this article. I hope it will help you in your study, and I hope you can support developepaer more.