Web API – Authentication using JWT

By | 21/07/2021

In this post, we will see how to implement token authentication in ASP.NET Core 5.0 Web API using JWT.
But first of all, what is JWT?
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
In a nutshell, JWT allows us to create Token to use in our applications for authentication.
Here, it is possible to find all information about JWT.

We open Visual Studio and we create a Web API project, called TestJWTFromScratch, and we start to add three libraries that we will use for the authentication:
Microsoft.IdentityModel.Tokens
System.IdentityModel.Tokens.Jwt
Microsoft.AspNetCore.Authentication.JwtBearer

Then, we add two entities called User and UserDTO that we will use for the login:

[USER.CS]

using System;
namespace TestJWTFromScratch.Core.Domain.Entities
{
    public class User
    {
        public Guid Id { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}



[USERDTO.CS]

namespace TestJWTFromScratch.Core.Domain.Entities
{
    public class UserDTO
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}



Now, we create the Login service where we will define two methods used to manage the Login and the Token creation:

[ICORELOGIN.CS]

using TestJWTFromScratch.Core.Domain.Entities;
namespace TestJWTFromScratch.Core.Application.Interfaces
{
    public interface ICoreLogin
    {
        User CheckUserLogin(UserDTO userInput);
        string GenerateToken(User user); 
    }
}



[CORELOGIN.CS]

using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using TestJWTFromScratch.Core.Application.Interfaces;
using TestJWTFromScratch.Core.Domain.Entities;

namespace TestJWTFromScratch.Core.Application.Commands
{
    public class CoreLogin : ICoreLogin
    {
        private readonly IConfiguration _configuration;
        public CoreLogin(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public User CheckUserLogin(UserDTO userInput)
        {
            User userOutput = null;

            // OBVIOUSLY, THIS IS A CODE ONLY FOR A DEMO!
            if(userInput.UserName == "UserDamiano" && userInput.Password == "Pass123")
            {
                userOutput = new User { Id = Guid.NewGuid(), UserName = userInput.UserName, Password = userInput.Password };
            }

            return userOutput;
        }

        public string GenerateToken(User user)
        {
            // Take the secret key
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Token:Key"]));

            // We define the algorithm to use for creating of the Token
            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            // We define the claims where we put some user's informations
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
            };

            // Here we define the Token
            var tokenDescription = new JwtSecurityToken(
                issuer: _configuration["Token:Issuer"],
                audience: _configuration["Token:Issuer"],
                claims,
                expires: DateTime.Now.AddMinutes(60),
                signingCredentials: credentials
                );

            // We create the Token
            var token = new JwtSecurityTokenHandler().WriteToken(tokenDescription);

            return token;
        }
    }
}



We have finished and now, we will create a controller called LoginController for managing the login:

[LOGINCONTROLLER.CS]

using Microsoft.AspNetCore.Mvc;
using TestJWTFromScratch.Core.Application.Interfaces;
using TestJWTFromScratch.Core.Domain.Entities;

namespace TestJWTFromScratch.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        private readonly ICoreLogin _coreLogin; 

        public LoginController(ICoreLogin coreLogin)
        {
            _coreLogin = coreLogin;
        }

        [HttpPost]
        public IActionResult Login(UserDTO userLogin)
        {
            var user = _coreLogin.CheckUserLogin(userLogin);

            if(user!=null)
            {
                var token = _coreLogin.GenerateToken(user);
                return Ok(new { token });
            }

            return Unauthorized();
        }
    }
}



Instead in the default controller, called WeatherForecastController, we will modify the method Get in order to use it only if we are authenticated:

[WEATHERFORECASTCONTROLLER.CS]

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace TestJWTFromScratch.Controllers
{
    [ApiController]
    [Route("[controller]")]
    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;
        }

        [Authorize]
        [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();
        }
    }
}



The last two things to do are:
1) Add in the [APPSETTINGS.JSON] the parameters for the Token

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Token": {
    "Key": "TheIncedibleKeyForThisTest",
    "Issuer": "ZoneOfDevelopment.com"
  }
}



2) Modify [STARTUP.CS] in order to use JWT:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System.Text;
using TestJWTFromScratch.Core.Application.Commands;
using TestJWTFromScratch.Core.Application.Interfaces;

namespace TestJWTFromScratch
{
    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.AddSingleton<ICoreLogin, CoreLogin>();

            services.AddControllers();

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


            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestJWTFromScratch", Version = "v1" });

                // To Enable authorization using Swagger (JWT)    
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                {
                    Name = "Authorization",
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header,
                    Description = "Enter 'Bearer' [space] and then your valid token in the text input below.\r\n\r\nExample: \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"",
                });
                c.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                          new OpenApiSecurityScheme
                            {
                                Reference = new OpenApiReference
                                {
                                    Type = ReferenceType.SecurityScheme,
                                    Id = "Bearer"
                                }
                            },
                            new string[] {}
                    }
                });

            });
        }

        // 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.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TestJWTFromScratch v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthentication();
            
            app.UseAuthorization();

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



We have done and now, if we run the Web API, this will be the result:

If we try to use the WeatherForecast method without Authentication, we will receive an error:

In order to use the method, we have to do:

1) Opening the Login method and run it, passing the correct credentials

2) Taking the generated Token

3) Using the Token for authorization

Now, if we try to use the WeatherForecast method, this will be the result:



Leave a Reply

Your email address will not be published. Required fields are marked *