JWT Authentication with ASP.NET Core 3.1 Identity for Web APIs

Creating the Project

We are using the ASP.NET Core 3.1 web application project with no authentication template because we want to do it by ourselves, from scratch.

Install-Package EntityFrameworkCore.SqlServerInstall-Package Microsoft.EntityFrameworkCore.ToolsInstall-Package Microsoft.EntityFrameworkCore.DesignInstall-Package Microsoft.AspNetCore.Identity.EntityFrameworkCoreInstall-Package AutoMapper.Extensions.Microsoft.DependencyInjectionInstall-Package AutoMapperInstall-Package Microsoft.AspNetCore.Authentication.JwtBearer

Configuring Database Context

Add the following configuration settings to your appsettings.json file

“ConnectionStrings”: {“DBContext”: “Server=(localdb)\\mssqllocaldb;Database=AuthDemoDB;Trusted_Connection=True;MultipleActiveResultSets=true”}
public class DBContext : DbContext{public DBContext(DbContextOptions<DBContext> options): base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfiguration(new RoleConfiguration());modelBuilder.Entity<IdentityUserRole<string>>().HasKey(p => new { p.UserId, p.RoleId });}public DbSet<User> Users { get; set; }}
public class RoleConfiguration : IEntityTypeConfiguration<IdentityRole>{public void Configure(EntityTypeBuilder<IdentityRole> builder){builder.HasData(new IdentityRole{Name = “Visitor”,NormalizedName = “VISITOR”},new IdentityRole{Name = “Administrator”,NormalizedName = “ADMINISTRATOR”});}}
services.AddDbContext<DBContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString(“DBContext”)));

Creating Identity Schema

Create a new directory named Models and add the following User class.

public class User : IdentityUser{public string FirstName { get; set; }public string LastName { get; set; }}
services.AddIdentity<User, IdentityRole>().AddEntityFrameworkStores<DBContext>();services.Configure<IdentityOptions>(options =>{options.Password.RequireDigit = false;options.Password.RequireNonAlphanumeric = false;options.Password.RequireUppercase = false;});
Add-Migration CreatingIdentitySchemeUpdate-Database

User Registration

To register users, we will create a new class named UserRegistrationModel in the Models directory.

public class UserRegistrationModel{public string FirstName { get; set; }public string LastName { get; set; }[Required(ErrorMessage = “Email is required”)][EmailAddress]public string Email { get; set; }[Required(ErrorMessage = “Password is required”)][DataType(DataType.Password)]public string Password { get; set; }[DataType(DataType.Password)][Compare(“Password”, ErrorMessage = “The password and confirmation password do not match.”)]public string ConfirmPassword { get; set; }}
// Auto Mapper Configurationsvar mappingConfig = new MapperConfiguration(mc =>{mc.AddProfile(new MappingProfile());});IMapper mapper = mappingConfig.CreateMapper();services.AddSingleton(mapper);
[Route(“api/[controller]”)][ApiController]public class AccountsController : ControllerBase{private readonly IMapper _mapper;private readonly UserManager<User> _userManager;private readonly IConfigurationSection _jwtSettings;public AccountsController(IMapper mapper, UserManager<User> userManager, IConfiguration configuration){_mapper = mapper;_userManager = userManager;_jwtSettings = configuration.GetSection(“JwtSettings”);}[HttpPost(“Register”)]public async Task<ActionResult> Register(UserRegistrationModel userModel){var user = _mapper.Map<User>(userModel);var result = await _userManager.CreateAsync(user, userModel.Password);if (!result.Succeeded){return Ok(result.Errors);}await _userManager.AddToRoleAsync(user, “Visitor”);return StatusCode(201);}
}

Authentication

To allow ASP.NET Core to provide authentication and authorization, add the following lines to the code after the line app.UseRouting(); under Configure function in Startup.cs

// Required for Authentication.app.UseAuthentication();app.UseAuthorization();
“JWTSettings”: {“securityKey”: “YourSecretKey”,“expiryInMinutes”: 600,“validIssuer”: “IssuerName”},
// JWT Configurationvar jwtSettings = Configuration.GetSection(“JwtSettings”);services.AddAuthentication(opt =>{opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(options =>{options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = false,ValidateAudience = false,ValidateLifetime = true,ValidateIssuerSigningKey = true,ValidIssuer = jwtSettings.GetSection(“validIssuer”).Value,ValidAudience = jwtSettings.GetSection(“validAudience”).Value,IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.GetSection(“securityKey”).Value))};});
public class UserLoginModel{[Required][EmailAddress]public string Email { get; set; }[Required][DataType(DataType.Password)]public string Password { get; set; }}
[HttpPost(“Login”)]public async Task<IActionResult> Login(UserLoginModel userModel){var user = await _userManager.FindByEmailAsync(userModel.Email);if (user != null && await _userManager.CheckPasswordAsync(user, userModel.Password)){var signingCredentials = GetSigningCredentials();var claims = GetClaims(user);var tokenOptions = GenerateTokenOptions(signingCredentials, await claims);var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);return Ok(token);}return Unauthorized(“Invalid Authentication”);}
private SigningCredentials GetSigningCredentials(){var key = Encoding.UTF8.GetBytes(_jwtSettings.GetSection(“securityKey”).Value);var secret = new SymmetricSecurityKey(key);return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);}private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, List<Claim> claims){var tokenOptions = new JwtSecurityToken(issuer: _jwtSettings.GetSection(“validIssuer”).Value,audience: _jwtSettings.GetSection(“validAudience”).Value,claims: claims,expires: DateTime.Now.AddMinutes(Convert.ToDouble(_jwtSettings.GetSection(“expiryInMinutes”).Value)),signingCredentials: signingCredentials);return tokenOptions;}private async Task<List<Claim>> GetClaims(User user){var claims = new List<Claim>{new Claim(ClaimTypes.Name, user.Email)};var roles = await _userManager.GetRolesAsync(user);foreach (var role in roles){claims.Add(new Claim(ClaimTypes.Role, role));}return claims;}

Authorization

To secure your application endpoints, add the following Authorize attributes to your controllers or controller actions as shown below.

[Authorize] // allow access to any logged in user[ApiController][Route(“[controller]”)]public class WeatherForecastController : ControllerBase{...
[Authorize(Roles = "Visitor")] // allow access only to visitor role[ApiController][Route(“[controller]”)]public class WeatherForecastController : ControllerBase{...

A Sample Draft Project

You can download a sample project at the following link.

References and Further Reading

  1. https://code-maze.com/asp-net-core-identity-series/
  2. https://code-maze.com/identity-asp-net-core-project/
  3. https://code-maze.com/user-registration-aspnet-core-identity/
  4. https://code-maze.com/authentication-aspnet-core-identity/
  5. https://code-maze.com/blazor-webassembly-series/
  6. https://code-maze.com/blazor-webassembly-role-based-authorization/
  7. https://code-maze.com/blazor-webassembly-authentication-aspnetcore-identity/

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store