Authentication and authorization are critical parts of any application. Especially when dealing with sensitive user information and access control.
ASP.NET Core provides robust support for implementing authentication and authorization mechanisms, enabling developers to secure their applications effectively.
This blog will guide you through the concepts and implementation of authentication and authorization using ASP.NET Core Identity and JWT (Json Web Tokens).
Authentication
Authentication is the process of validating a user's identity. In ASP.NET Core, it involves verifying credentials and generating a token or session upon successful login.
Authorization
Authorization determines whether an authenticated user has permission to access a resource or perform a specific action. It typically involves roles or policies.
Implementing Authentication and Authorization in ASP.NET Core
We implement a basic authentication and role-based authorization system using ASP.NET Core Identity and JWT.
- Open visual studio, goto create new project and select APS.NET Core Wen API
- Add project name and go to next.
3. Select the You framework. I am selecting.NET8.0 (Long Term Support).
Add necessary NuGet packages
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Relational
- Microsoft.EntityFrameworkCore.Tools
- Pomelo.EntityFrameworkCore.MySql
- System.IdentityModel.Tokens.Jwt
Configure Program.cs database and Identity
//Database
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"), new MySqlServerVersion(new Version(10,4,25))));
//Identity and JWT Token
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
var key = builder.Configuration.GetValue<string>("ApiSettings:Secret");
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = builder.Configuration["ApiSettings:Audience"],
ValidIssuer = builder.Configuration["ApiSettings:Issuer"],
ClockSkew = TimeSpan.Zero,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
});
// add pipeline for authentication and authorization
app.UseAuthentication();
app.UseAuthorization()
Creating the User Model
Create a custom user model that extends IndetityUser:
// Application User
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
// Register Request Dto
public class RegisterRequestDto
{
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Password { get; set; }
public string? Role { get; set; }
}
// User Dto
public class UserDto
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
}
// Login Request Dto
public class LoginRequestDto
{
public string UserName { get; set; }
public string Password { get; set; }
}
// Login Response Dto
public class LoginResponseDto
{
public UserDto User { get; set; }
public string Token { get; set; }
}
Setting Up the Controller
User Registration
The Register endpoint allows new users to sign up and assigns them roles:
[HttpPost("register")]
public async Task<ActionResult> Register([FromBody] RegisterRequestDto model)
{
try
{
var userUnique = await _userManager.FindByEmailAsync(model.Email);
if (userUnique != null)
{
return BadRequest(new { Message = "User is already exits" });
}
ApplicationUser user = new()
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
PhoneNumber = model.PhoneNumber,
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded) {
if (!_roleManager.RoleExistsAsync("Admin").GetAwaiter().GetResult()) {
_roleManager.CreateAsync(new IdentityRole("Admin")).GetAwaiter().GetResult();
_roleManager.CreateAsync(new IdentityRole("User")).GetAwaiter().GetResult();
}
if (model.Role != null) {
await _userManager.AddToRoleAsync(user, model.Role);
}
else
{
await _userManager.AddToRoleAsync(user, "User");
}
}
else
{
return BadRequest(new { result.Errors });
}
return Ok(new { Message = "User registration successfully" });
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
Login User
The Login endpoint validates credentials and generates a JWT token
[HttpPost("login")]
public async Task<ActionResult> Login([FromBody] LoginRequestDto model)
{
try
{
var user = await _userManager.FindByNameAsync(model.UserName);
if (user == null) {
return BadRequest(new { Message = "User not found!" });
}
bool isValid = await _userManager.CheckPasswordAsync(user, model.Password);
if (!isValid) {
return BadRequest(new {Message="Invalid credentials." });
}
var userDto = new UserDto()
{
Id =user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
PhoneNumber = user.PhoneNumber
};
var roles = await _userManager.GetRolesAsync(user);
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(secretKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, model.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Role, roles.FirstOrDefault())
}),
Expires = DateTime.UtcNow.AddDays(7),
Audience = Audience,
Issuer = Issuer,
SigningCredentials = new(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
};
var token = tokenHandler.CreateToken(tokenDescriptor);
LoginResponseDto loginResponse = new LoginResponseDto()
{
Token = tokenHandler.WriteToken(token),
User = userDto
};
return Ok(loginResponse);
}
catch (Exception ex) {
return BadRequest(ex.Message);
}
}
User Profile
The Profile endpoint is protected and accessible only to authenticated users:
[HttpGet("profile")]
[Authorize]
public async Task<ActionResult> Profile()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound(new { Message = "User not found" });
}
return Ok(user);
}
Final AuthController
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.Diagnostics.CodeAnalysis;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using TestingCrud.Model;
using TestingCrud.Model.dto;
namespace TestingCrud.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private UserManager<ApplicationUser> _userManager;
private RoleManager<IdentityRole> _roleManager;
private string secretKey;
private string Audience;
private string Issuer;
public AuthController(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager,
IConfiguration configuration)
{
_roleManager = roleManager;
_userManager = userManager;
secretKey = configuration.GetValue<string>("ApiSettings:Secret");
Audience = configuration.GetValue<string>("ApiSettings:Audience");
Issuer = configuration.GetValue<string>("ApiSettings:Issuer");
}
[HttpPost("register")]
public async Task<ActionResult> Register([FromBody] RegisterRequestDto model)
{
try
{
var userUnique = await _userManager.FindByEmailAsync(model.Email);
if (userUnique != null)
{
return BadRequest(new { Message = "User is already exits" });
}
ApplicationUser user = new()
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
PhoneNumber = model.PhoneNumber,
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded) {
if (!_roleManager.RoleExistsAsync("Admin").GetAwaiter().GetResult()) {
_roleManager.CreateAsync(new IdentityRole("Admin")).GetAwaiter().GetResult();
_roleManager.CreateAsync(new IdentityRole("User")).GetAwaiter().GetResult();
}
if (model.Role != null) {
await _userManager.AddToRoleAsync(user, model.Role);
}
else
{
await _userManager.AddToRoleAsync(user, "User");
}
}
else
{
return BadRequest(new { result.Errors });
}
return Ok(new { Message = "User registration successfully" });
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
[HttpPost("login")]
public async Task<ActionResult> Login([FromBody] LoginRequestDto model)
{
try
{
var user = await _userManager.FindByNameAsync(model.UserName);
if (user == null) {
return BadRequest(new { Message = "User not found!" });
}
bool isValid = await _userManager.CheckPasswordAsync(user, model.Password);
if (!isValid) {
return BadRequest(new {Message="Invalid credentials." });
}
var userDto = new UserDto()
{
Id =user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
PhoneNumber = user.PhoneNumber
};
var roles = await _userManager.GetRolesAsync(user);
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(secretKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, model.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Role, roles.FirstOrDefault())
}),
Expires = DateTime.UtcNow.AddDays(7),
Audience = Audience,
Issuer = Issuer,
SigningCredentials = new(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
};
var token = tokenHandler.CreateToken(tokenDescriptor);
LoginResponseDto loginResponse = new LoginResponseDto()
{
Token = tokenHandler.WriteToken(token),
User = userDto
};
return Ok(loginResponse);
}
catch (Exception ex) {
return BadRequest(ex.Message);
}
}
[HttpGet("profile")]
[Authorize]
public async Task<ActionResult> Profile()
{
try
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userManager.FindByIdAsync(userId);
return Ok(user);
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
}
}
File Download: https://github.com/nitish9625/Dotnet-Authentication-
Conclusion
You can implement robust authentication and role-based authorization in your ASP.NET Core application.This ensure your application is secure and user roles are managed efficiently.
If you have any questions or concerns, please feel free to let us know. Our team will connect with you shortly to assist.
Login to leave a comment.