Authorization in ASP.NET Core
• Complete re-write• support for unauthorized vs forbidden
• better separation of business code and authorization logic
• re-usable policies
• resource/action based authorization
• DI enabled
[Authorize]
• [Authorize] attribute still supported• [AllowAnonymous] also still
supported
• Custom implementations no longer supported• More on this in a bit…
[Authorize]public class HomeController : Controller{
[AllowAnonymous]public IActionResult Index(){
return View();}
[Authorize(Roles = "Sales")]public IActionResult About(){
return View(User);}
}
Unauthorized vs. Forbidden
Unauthorized : User is anonymous
302 to LoginPath
Forbidden : User is authenticated
302 to AccessDeniedPath
public void Configure(IApplicationBuilder app){
app.UseCookieAuthentication(new CookieAuthenticationOptions{
AuthenticationScheme = "Cookies",AutomaticAuthenticate = true,AutomaticChallenge = true,LoginPath = new PathString("/Account/Login"),AccessDeniedPath = new PathString("/Home/Forbidden"),
});}
Role-based authorization in ASP.NET
• Roles were promoted as the primary approach to authorization• Still supported in ASP.NET Core
• High coupling to roles is an anti-pattern
public class CustomerController : Controller{
[Authorize(Roles = "Sales")]public IActionResult Add(string name){
return View();}
}
Policy-based authorization
• Framework to promote better authorization style• Decoupled from other application logic
• Reusable from many locations in application code
public class CustomerController : Controller{
[Authorize(Policy = "ManageCustomers")]public IActionResult Add(string name){
return View();}
}
Defining a policy
• List of requirements• All must pass
• Registered in DI• Can be encapsulated
in separate class
public void ConfigureServices(IServiceCollection services){
services.AddAuthorization(options =>{
options.AddPolicy("ManageCustomers", policy =>{
policy.RequireAuthenticatedUser().RequireRole("Sales").RequireClaim("level", "senior").RequireAssertion(ctx =>{
return ctx.User.HasClaim("location", "USA") || ctx.User.IsInRole("Admin");
});});
});}
Programmatically using policies
• IAuthorizationService provides access to authorization framework
public class CustomerController : Controller{
private readonly IAuthorizationService _authz;
public CustomerController(IAuthorizationService authz){
_authz = authz;}
public async Task<IActionResult> Add(string name){
var allowed = await _authz.AuthorizeAsync(User, "ManageCustomers");if (!allowed) return Challenge();
return View();}
}
Razor also supports injection
@using Microsoft.AspNetCore.Authorization@inject IAuthorizationService _authz
@if (await _authz.AuthorizeAsync(User, "ManageCustomers")){
<div><a href="/Customers/ViewAll">Customer List</a>
</div>}
Custom requirements
• Requirements can be custom and/or dynamic
public class LocationRequirement : IAuthorizationRequirement{
public string RequiredLocation { get; set; }}
public async Task<IActionResult> ViewCustomers(string country = "USA"){
var locationRequirement = new LocationRequirement { RequiredLocation = country };var allowed = await _authz.AuthorizeAsync(User, null, locationRequirement);if (!allowed) return Challenge();
return View();}
AuthorizationHandlers implement logic
public class LocationRequirementHandler : AuthorizationHandler<LocationRequirement>{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocationRequirement requirement)
{var userLocation = context.User.FindFirst("location")?.Value;if (userLocation == requirement.RequiredLocation){
context.Succeed(requirement);}
return Task.FromResult(0);}
}
services.AddTransient<IAuthorizationHandler, LocationRequirementHandler>();
Resource-based Authorization
Subject ObjectOperation
- client ID- subject ID- scopes
- more claims
+ DI
- read- write- send via email- ...
- ID- owner
- more properties
+ DI
Define resource and operations
• Resource is just a class
• Operations are named OperationAuthorizationRequirement
public class Customer{
public int Id { get; set; }public string Name { get; set; }public string SalesRepId { get; set; }
public static OperationAuthorizationRequirement Edit= new OperationAuthorizationRequirement { Name = "Edit" };
public static OperationAuthorizationRequirement Delete= new OperationAuthorizationRequirement { Name = "Delete" };
}
Implement resource-based logic & add to DIpublic class CustomerHandler : AuthorizationHandler<OperationAuthorizationRequirement, Customer>{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement operation, Customer customer)
{if (operation == Customer.Edit){
var userId = context.User.FindFirst("userId").Value;if (userId == customer.SalesRepId){
context.Succeed(operation);}
}
return Task.FromResult(0);}
}
services.AddTransient<IAuthorizationHandler, CustomerHandler>();
Invoke resource-based authorizationpublic class CustomerController : Controller{
private readonly IAuthorizationService _authz;
public CustomerController(IAuthorizationService authz){
_authz = authz;}
[HttpPost]public async Task<IActionResult> Update(Customer customer){
var allowed = await _authz.AuthorizeAsync(User, customer, Customer.Edit);if (!allowed) return Challenge();
return View();}
}
Top Related