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
- Install .NET Core 2.0.0 SDK or above.
- Install Visual Studio Code.
- SQL Server.
- 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
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.
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
{
"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.
Great work!, your post are always helping the people. Keep up the nice job. I will be coming back more ofter