Authorization is a crucial aspect of securing your ASP.NET Core 8 Web API, ensuring that authenticated users have the necessary permissions to access specific resources. This guide covers role-based authorization, policy-based authorization, custom authorization handlers, and best practices.
Authorization is the process of determining what an authenticated user is allowed to do. It ensures that users can only access the resources and perform the actions they are permitted to.
Authentication: The process of verifying the identity of a user or service. It answers the question, "Who are you?"
Authorization: The process of determining what an authenticated user is allowed to do. It answers the question, "What can you do?"
Note: Authentication is always performed before authorization. Once the identity of the user is confirmed, authorization policies determine what resources the user can access.
Role-based authorization uses roles to control access to resources. Users are assigned roles, and those roles dictate what they can do.
3.1 Configure Role-Based Authorization in Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Admin"));
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
[Authorize(Roles = "Admin")]
[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
[HttpGet]
public IActionResult GetAdminData()
{
var data = new List<string> { "Admin Data1", "Admin Data2" };
return Ok(data);
}
}
Policy-based authorization allows for more complex access control scenarios by defining policies with requirements and handlers.
4.1 Configure Policy-Based Authorization in Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Admin"));
options.AddPolicy("Over18Policy", policy => policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
[Authorize(Policy = "Over18Policy")]
[ApiController]
[Route("api/[controller]")]
public class RestrictedController : ControllerBase
{
[HttpGet]
public IActionResult GetRestrictedData()
{
var data = new List<string> { "Restricted Data1", "Restricted Data2" };
return Ok(data);
}
}
Custom authorization handlers allow you to implement complex authorization logic.
5.1 Create a Custom Handler Custom Handler:
public class CustomAuthorizationHandler : AuthorizationHandler<CustomRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
{
// Custom authorization logic
if (context.User.HasClaim(c => c.Type == "CustomClaim" && c.Value == requirement.AllowedValue))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class CustomRequirement : IAuthorizationRequirement
{
public string AllowedValue { get; }
public CustomRequirement(string allowedValue)
{
AllowedValue = allowedValue;
}
}
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CustomPolicy", policy => policy.Requirements.Add(new CustomRequirement("AllowedValue")));
});
builder.Services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();
[Authorize(Policy = "CustomPolicy")]
[ApiController]
[Route("api/[controller]")]
public class CustomAuthController : ControllerBase
{
[HttpGet]
public IActionResult GetCustomData()
{
var data = new List<string> { "Custom Data1", "Custom Data2" };
return Ok(data);
}
}
Claims-based authorization checks the value of specific claims and grants access based on those claims.
6.1 Add Claims to JWT Token
public string GenerateJwtToken(string username, string role, DateTime dateOfBirth)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, role),
new Claim(ClaimTypes.DateOfBirth, dateOfBirth.ToString("yyyy-MM-dd"))
};
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ClaimsController : ControllerBase
{
[HttpGet]
[Authorize(Policy = "Over18Policy")]
public IActionResult GetClaimsData()
{
var data = new List<string> { "Claims Data1", "Claims Data2" };
return Ok(data);
}
}
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllers();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Admin"));
options.AddPolicy("Over18Policy", policy => policy.Requirements.Add(new MinimumAgeRequirement(18)));
options.AddPolicy("CustomPolicy", policy => policy.Requirements.Add(new CustomRequirement("AllowedValue")));
});
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
builder.Services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
public class AuthService
{
private readonly IConfiguration _configuration;
public AuthService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateJwtToken(string username, string role, DateTime dateOfBirth)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, role),
new Claim(ClaimTypes.DateOfBirth, dateOfBirth.ToString("yyyy-MM-dd"))
};
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class CustomRequirement : IAuthorizationRequirement
{
public string AllowedValue { get; }
public CustomRequirement(string allowedValue)
{
AllowedValue = allowedValue;
}
}
public class CustomAuthorizationHandler : AuthorizationHandler<CustomRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "CustomClaim" && c.Value == requirement.AllowedValue))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
[Authorize(Roles = "Admin")]
[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
[HttpGet]
public IActionResult GetAdminData()
{
var data = new List<string> { "Admin Data1", "Admin Data2" };
return Ok(data);
}
}
[Authorize(Policy = "Over18Policy")]
[ApiController]
[Route("api/[controller]")]
public class RestrictedController : ControllerBase
{
[HttpGet]
public IActionResult GetRestrictedData()
{
var data = new List<string> { "Restricted Data1", "Restricted Data2" };
return Ok(data);
}
}
[Authorize(Policy = "CustomPolicy")]
[ApiController]
[Route("api/[controller]")]
public class CustomAuthController : ControllerBase
{
[HttpGet]
public IActionResult GetCustomData()
{
var data = new List<string> { "Custom Data1", "Custom Data2" };
return Ok(data);
}
}
Authorization is a vital part of securing your ASP.NET Core 8 Web API. By implementing role-based and policy-based authorization, as well as custom authorization handlers, you can ensure that only authorized users can access specific resources. Following best practices such as using secure policies, centralizing authorization logic, and regularly reviewing roles and policies will help you build secure and reliable web APIs. This comprehensive guide provides the tools and knowledge to implement effective authorization in your ASP.NET Core 8 Web API projects.