How to build ASP.NET Core application with JWT authentication

The purpose of this article is to explain in very simple step how to setup a ASP.NET Core application that expose some REST API and protect them with a simple JWT authentication.

Prerequisites

Create project

This section explains how to create a new project with minimum necessary NuGet pacakges to be able to create a REST services API application with JWT authentication

  1. Open visual studio and create a new “ASP.NET Core Web Application
How to build ASP.NET Core application with JWT authentication: choose a project
1
  1. Give it a name
2
  1. Choose “API” template
3
  1. Change launch setting to use project instead IIS Express
  2. Go to Properties, edit launchSetting.json file to set "launchBrowser": false
4
5
  1. Make sure you’re using the right framework netcore 3.1
    1. Go to your project’s property
    2. Under section “Application“, look for “Target framework” drop-down and make sure “.NET Core 3.1” is selected
6.1
6.2
  1. Test your initial setting running your application for first time (“F5” keyboard shortcut). If asked to allow ASP.NET SLL Certificate, install it.
    You should have a screen like the below one
7
7 – trust certificate
  1. Add JWT NuGet package
    1. from solution explorer, right click on “decencies
    2. Manage NuGet packages…
    3. look for “JWT”
    4. Choose the latest version of “Microsoft.AspNetCore.Authentication.JwtBearer
8.1 – 8.2

8.4

Add authentication to project

This section explain how to configure JWT on the app

  1. Add new class “RegisterJWT“.
  2. Declare it static: we are going to use this class to configure and add a middleware on Startup class using method extension.

The idea is to keep the Startup.cs file as clean as possible and isolate the various configuration steps as much as possible.
We try to apply the philosophy of “Separation of concerns” as much as possible.

  1. Create a method extension that allow us to easily configure JWT on our project. In this method we are going to:
    1. Add JWT authentication with JwtBearerDefaults.AuthenticationScheme
    2. then, configure JWT TokenValidationParameters
      • ValidateIssuer: Validate the server that generates the token
      • ValidateAudience: Validate the recipient of token is authorized to receive
      • ValidateLifetime: Check if token is not expired and the signing key of the issuer is valid
      • ValidateIssuerSigningKey: Validate signature of the token
      • ValidIssuer, ValidAudience, IssuerSigningKey: values for the issuer, audience, signing key. For this example, we stored these values in appsettings.json file.
public static void ConfigureJWT(this IServiceCollection self, IConfiguration configuration)
{
    self.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = configuration["Jwt:Issuer"],
                ValidAudience = configuration["Jwt:Issuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]))
            };
        });
}
  // --------
  // JWT section
  // --------

  "Jwt": {
    "Key": "BGy+7Eb7cyqgZy?N6Sw7Z!&dC95Utjk#xVp8yTxWNUMqd%f*4T$kMuk!jAJMyUVg+R=+3EkL374$Q4vJ=&2$HRV&fu3sZxFZ6S?M+SZK*bM*Nq^KpphsguWczRR*MjcHCbRkQ$V#BDKykQW=e%H=L&5FYf%$3jbK^gGrWvXnYkCM5duj!A6A#EhUjp*aQWnP2NYRBVDs8xNYNwmJ+8jmwucG*qc$fZdk3VGKR@_9x@xK7BAK$#aSq9_zMqFTeP=cE$VqrqMks4DBzgtbCTQVAVEeg%wRYfg9qY=C$ge8v!Dr*ZfRbEsNv?pvrjCnK+jqgZXYpXgnettxpsq3_!-2KFRdGuKWu@6&Szm&2c*2**^R!mzwtXWUg#Ce%DW*=G7^#n?A+3#26BPCgvHSfuk$-#kSGVXh4#!a_VWts&Je$mDBtR+8f@4C*@f=XfMRJBkys^buWNS8w=MbRRKzZAU%R%Qy+LH5$%y6m%-am7vv!D47KmRe@G@UKF=g*_xpv3*Q",
    "Issuer": "AspNetCoreJWT"
  },
  1. Create a method extension that allow us to easily add JWT on our project. In this method we are going to simply declare to “UseAuthenication
public static void UseJWT(this IApplicationBuilder self)
{
    self.UseAuthentication();
}
  1. Add those methods to Startup class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace AspNetCoreJWT
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.ConfigureJWT(Configuration);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();
            
            app.UseJWT();

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

Create JWT token

This section explains how to crete JWT token with simple login controller

  1. Add new “Services” folder
  2. Add new “Dto” folder: in this folder we put all our Data Transfer Object. This allows us to separate the model layer from the API parameters and let us free to hide or re-map some model fields if needs.
  3. Create a class inside Dto folder, and name it “UserLogin“: we are going to use this class to bind the credentials to the API
public class UserLogin
{
    public string UserName { get; set; }
    public string Password { get; set; }
}
  1. Create new interface file “ILoginService” and a class LoginService” that implements the interface: this allows us to use them via dependency injection
public interface ILoginService
{
    bool Authenticate(UserLogin user);

    string GenerateJWT(UserLogin user);
}

Authenticate

This is a moked authentication: since this is only a tutorial, we don’t need some complicated logic, so username and password are harcoded in here.

public bool Authenticate(UserLogin user)
{
    return string.Equals(user.UserName, "bob", StringComparison.InvariantCultureIgnoreCase)
            && string.Equals(user.Password, "123", StringComparison.InvariantCultureIgnoreCase);
}

GenerateJWT

This method create the JWT token that allow us to protect our API with Authentication Bearer Token.
After generate the JWT token, who calls our API have to include this token to the request using the Authorization http header, with Bearer <token>
In the JwtSecurityToken we can include some information called Claims.

Claims are pieces of information asserted about a subject. For example, an ID Token (which is always a JWT) may contain a claim called name that asserts that the name of the user authenticating is “John Doe”.

In a JWT, a claim appears as a name/value pair where the name is always a string and the value can be any JSON value. Generally, when we talk about a claim in the context of a JWT, we are referring to the name (or key). For example, the following JSON object contains three claims (subnameadmin):

{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
}


We can add what we want, but keep we have to keep in mind that this information is going to be included on each HTTP request header because is part of JWT token, so we have a limit of byte we can use, depends on which server we are going to use ( Apache: 8KB, IIS depends on from configuration)
Typically, the information that are sent with token are listed here: https://docs.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.jwt.jwtregisteredclaimnames?view=azure-dotnet

public string GenerateJWT(UserLogin user)
{
    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

    List<Claim> claims = new List<Claim>
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName)
    };

    var token = new JwtSecurityToken(_config["Jwt:Issuer"],
        _config["Jwt:Issuer"],
        claims,
        expires: DateTime.Now.AddMinutes(120),
        signingCredentials: credentials);

    return new JwtSecurityTokenHandler().WriteToken(token);
}
  1. Create a class inside Dto folder, and name it “Register“: we are going to use this class to register the dependency injection on startup.cs file.
  2. Make this class static and define a method extension that allows you to register all DI services.
public static class Register
{
    public static void ConfigureAppServices(this IServiceCollection self, IConfiguration configuration)
    {
        self.AddSingleton(typeof(ILoginService), typeof(LoginService));
    }
}
  1. On Startup class use new method extension to register all our services
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.ConfigureJWT(Configuration);

    //
    services.ConfigureAppServices(Configuration);
}
  1. Under “Controllers” folder add new empty controller and call it “LoginController
7
7
  1. Inject ILoginService interface
  2. Create new method “Login” which allow us to use finally let the user able to authenticate.
    Let this methods accept anonymous request, because, obviously, we can’t force authentication on login.
[AllowAnonymous]
[HttpPost("Login")]
public IActionResult Login([FromBody]UserLogin user)
{
    IActionResult response = Unauthorized();

    if (_service.Authenticate(user))
    {
        var tokenString = _service.GenerateJWT(user);
        response = Ok(new { token = tokenString });
    }

    return response;
}
  1. Test the login with insomnia rest client (here you’ll find the workspace), postman, or simply via command line
curl --request POST \
  --url https://localhost:5001/api/login/login \
  --header 'content-type: application/json' \
  --data '{
	"UserName": "John Doe",
	"Password": "123"
}'
  1. you should receive a response like this:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKb2huIERvZSIsImVtYWlsIjoiSm9obiBEb2VAYXNkLml0IiwiZXhwIjoxNTc2OTQ3MzgxLCJpc3MiOiJBc3BOZXRDb3JlSldUIiwiYXVkIjoiQXNwTmV0Q29yZUpXVCJ9.IWZzZUq7wMP6OnVrYB2RQ2AWDO5eWElgUuNPOMKX9Zw"
}

Test your JWT Authentication

  1. Add a few middleware to your Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>()?.HttpContext?.User);

    //
    services.ConfigureJWT(Configuration);
    services.ConfigureAppServices(Configuration);
}

This allows you to retrieve claims value from http context (see below)

  1. Create a new controller “TestController“, put this controller under Authorization
  2. Inject IPrincipal to be able to access JWT claims:
namespace AspNetCoreJWT.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public class TestController : ControllerBase
    {
        private IPrincipal _principal;
        public TestController(IPrincipal principal)
        {
            _principal = principal;
        }        
    }
}
  1. Create a test method to call and check for JWT value and validity
    we simply retrive the claims “JwtRegisteredClaimNames.Sub” from ClaimsPrincipal
[HttpGet("TestToken")]
public IActionResult TestToken()
{
    string username = (_principal as ClaimsPrincipal)?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value ?? "unknown";

    return Ok($"Welcome: {username}");
}
  1. in order to let this (_principal as ClaimsPrincipal)?.FindFirst(JwtRegisteredClaimNames.Sub)” works go back to your RegisterJWT class and add: “System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();” into ConfigureJWT method. You can find here the reason
    Now your class looks like:
public static class RegisterJWT
{
    public static void ConfigureJWT(this IServiceCollection self, IConfiguration configuration)
    {
        System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

        self.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = configuration["Jwt:Issuer"],
                    ValidAudience = configuration["Jwt:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]))
                };
            });
    }

    public static void UseJWT(this IApplicationBuilder self)
    {
        self.UseAuthentication();
    }
}
  1. Test your controller with insomnia (here you’ll find the workspace), postman or simply via command line:
curl --request GET \
  --url https://localhost:5001/api/test/TestToken \
  --header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKb2huIERvZSIsImVtYWlsIjoiSm9obiBEb2VAYXNkLml0IiwiZXhwIjoxNTc2OTQ3MzgxLCJpc3MiOiJBc3BOZXRDb3JlSldUIiwiYXVkIjoiQXNwTmV0Q29yZUpXVCJ9.IWZzZUq7wMP6OnVrYB2RQ2AWDO5eWElgUuNPOMKX9Zw'

Leave a Comment