Token Based Authentication In ASP.NET Core WebAPI

Introduction

In this article, We will learn. Token-Based Authentication and Authorization in ASP.NET Core WebApi 2.0. so, we use the Entity Framework Core and SQL Server. in this post, we will understand step by step JWT token based Authentication.

Prerequisites

  1. Install .NET Core 2.0.0 SDK or above.
  2. Install Visual Studio Code.
  3. SQL Server.
  4. Download Postman for testing API calls.

Create the ASP.NET Core 2.0 WebApi

Let’s, go to visual studio code and open the terminal window. After that, we’ll create a new project, for our CoreWebApi. an example project using the following command.

C:Users\Monk> mkdir CoreWebApi

C:Users\Monk> cd .\CoreWebApi\

C:Users\Monk>dotnet new webapi -o CoreWebApi

Creating the Database and Table

So, before going to further, Let’s go to SQL Server and Create a database SampleDb. inside the database. We will be using a database table to store all the Identity records of the Users.

Add Entity Framework Core and SQL Server dependencies

Now, we need to add the Entity Framework Core and SQL Server dependencies. into the new .csproj file. and put the following code inside it.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <UserSecretsId>aspnet-WebApiJwt-9EB56A08-D5EE-4EEF-B339-DEE0A5CA6277</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
    <PackageReference Include="MySql.Data.Core" Version="7.0.4-IR-191" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.0.0-rc-*" />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
  </ItemGroup>
</Project>

After that, go to the terminal window and create new directory name Data. in our project. and then, inside create ApplicationDbContext.cs file in it.

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
namespace CoreWebApi.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseMySql(GetConnectionString());
        }
        
        private static string GetConnectionString()
        {
            const string DbName = "CoreWebApi";
            const string DbUser = "";
            const string DbPass = "";
            
            return $"Server=localhost;" +
                   $"database={DbName};" +
                   $"uid={DbUser};" +
                   $"pwd={DbPass};" +
                   $"pooling=true;";
        }
    }
}

The ApplicationDbContext class extends from the base class IdentityDbContext. So, we do not have to create manually tables in our database.

Configure Middleware in Startup.cs file

So, we need to Configure ApplicationDbContext into the Configure method and also register services inside it. and let’s put the following code inside the Startup.cs file.

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using CoreWebApi.Data;

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

        public IConfiguration Configuration { get; }

        //this method is Use to register services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add DbContext middleware 
            services.AddDbContext<ApplicationDbContext>();
            
            // Add Identity authentication middleware
            services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            // Add MVC middleware
            services.AddMvc();
        }
       // this method use to configure the all HTTP request pipeline.
        public void Configure(
            IApplicationBuilder app, 
            IHostingEnvironment env,
            ApplicationDbContext dbContext
        )
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseMvc();
            //Create context tables
            dbContext.Database.EnsureCreated();
        }
    }
}

Now, let’s go to terminal and Run our project. and you will see these all identity tables are created automatically.

identity tables for authentication in asp.net core application

After that, we need to add JWT Authentication inside the ConfigureServices() method. So, let’s go and add the following code inside the Startup.cs file.

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using CoreWebApi.Data;

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

        public IConfiguration Configuration { get; }

        //this method is Use to register services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add DbContext 
            services.AddDbContext<ApplicationDbContext>();
            
            // Add Identity 
            services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            
            // Add JWT Authentication 
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
            services
                .AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    
                })
                .AddJwtBearer(cfg =>
                {
                    cfg.RequireHttpsMetadata = false;
                    cfg.SaveToken = true;
                    cfg.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidIssuer = Configuration["JwtIssuer"],
                        ValidAudience = Configuration["JwtIssuer"],
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
                        ClockSkew = TimeSpan.Zero // remove delay of token when expire
                    };
                });

            // Add MVC 
            services.AddMvc();
        }

        // this method use to configure the all HTTP request pipeline.
        public void Configure(
            IApplicationBuilder app, 
            IHostingEnvironment env,
            ApplicationDbContext dbContext
        )
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // Use Authentication middleware ======
            app.UseAuthentication();
            app.UseMvc();

            //Create context tables
            dbContext.Database.EnsureCreated();
        }
    }
}

First, we initialize the IConfiguration interface. in, the Startup controller, and then, We use the Configuration[“JwtIssuer”] and Configuration[“JwtKey”] stored JSON. key & values inside, the appsettings.json file.

{
  "JwtKey": "secret key is always secret key",
  "JwtIssuer": "https://monkelite.com",
  "JwtExpireDays": 1
}

Adding the Controller to the Application

Let’s, create a new controller inside the Controllers folder. So, Right-Click on the Controllers folder and then, select “New File”. Name it, AccountController.cs for authentication. so, it will contain /Account/Login and /Account/Register endpoints. and It will produce JWT tokens using our GenerateJwtToken() method. and check the login and register operations. see the following code.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

namespace CoreWebApi.Controllers
{
    [Route("[controller]/[action]")]
    public class AccountController : Controller
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IConfiguration _configuration;

        public AccountController(
            UserManager<IdentityUser> userManager,
            SignInManager<IdentityUser> signInManager,
            IConfiguration configuration
            )
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _configuration =     configuration;
        }
        
        [HttpPost]
        public async Task<object> Login([FromBody] LoginDto model)
        {
            var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false);
            
            if (result.Succeeded)
            {
                var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email);
                return await GenerateJwtToken(model.Email, appUser);
            }
            
            throw new ApplicationException("INVALID_LOGIN_ATTEMPT");
        }
       
        [HttpPost]
        public async Task<object> Register([FromBody] RegisterDto model)
        {
            var user = new IdentityUser
            {
                UserName = model.Email, 
                Email = model.Email
            };
            var result = await _userManager.CreateAsync(user, model.Password);

            if (result.Succeeded)
            {
                await _signInManager.SignInAsync(user, false);
                return await GenerateJwtToken(model.Email, user);
            }
            
            throw new ApplicationException("UNKNOWN_ERROR");
        }
        
        private async Task<object> GenerateJwtToken(string email, IdentityUser user)
        {
            var claims = new List<Claim>
            {
                new Claim(JwtRegisteredClaimNames.Sub, email),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(ClaimTypes.NameIdentifier, user.Id)
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpireDays"]));

            var token = new JwtSecurityToken(
                _configuration["JwtIssuer"],
                _configuration["JwtIssuer"],
                claims,
                expires: expires,
                signingCredentials: creds
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }	
}

Adding the Model to the Application

Now, let’s add the following two Model classes inside the Models folder. Then, Right-click on the Models folder and select “New File”. Name it named LoginDto.cs

using System;    
using System.Collections.Generic;    
using System.ComponentModel.DataAnnotations;    
using System.Linq;    
using System.Threading.Tasks;    
    
namespace CoreWebApi.Models    
{   	 
	public class LoginDto
    {
        [Required]
        public string Email { get; set; }

        [Required]
        public string Password { get; set; }
    }
}

And another model is RegisterDto.

using System;    
using System.Collections.Generic;    
using System.ComponentModel.DataAnnotations;    
using System.Linq;    
using System.Threading.Tasks;    
    
namespace CoreWebApi.Models    
{   	 
	public class RegisterDto
    {
        [Required]
        public string Email { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "Please enter at least 10 characters", MinimumLength = 10)]
        public string Password { get; set; }
    }
}	

Now, Let’s open the Postman tool. Then, we need to Test our Register method into the API URL box. Then, click the blue SEND button.

The request is successful, we see the response something like that:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyb2JpbkBtb25rZWxpdGUuY29tIiwianRpIjoiYzAxODEyZDgtMjcyOS00YmFhLThhZDAtOTVlMjhiODM3NzU0IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiJyNDgxNGI2MS04YjI5LTQ1NTctOWY1OS00NGRjOWYwMjQ1OWUiLCJleHAiOjEwMTE0MjA1NDcsImlzcyI6Imh0dHA6Ly9tb25rZWxpdGUuY29tIiwiYXVkIjoiaHR0cDovL2FuZ3VsYXIubW9ua2VsaXRlLmNvbSJ9.v8YLTMTUraD7KqoHTskvcg9X_zH5WdWkcpGuHHeqYKM

this returned token stored in your client application localStorage. and it will send, all the requests with HTTP header Authorization: Bearer “token”

In our application, we need to add Authorization using the Authorize attribute. for only signed in users.

[Authorize]
[HttpGet]
public async Task<object> Protected()
{
    return "Protected method";
}

when we send the incorrect or Bad credentials into the token. it will get the HTTP 401 unauthorized error.

So, in this post, you were learned. how to implement token-based authorization and authentication using the entity framework. in ASP.NET Core WebApi with VS Code.

One Comment

Leave a Reply

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