I am attempting to write a Policy-based Authorization Handler. The business logic of the handler needs to use the record id
of the current request that is passed in through the default route.
[Authorize(Roles = "TaskAdmin", Policy = "RecordOwner")]
public IActionResult Index(int id) // <-- Need this id
{
// <snip>
return View();
}
Here is the class where I need to access the id
route value.
public class RecordOwnerHandler : AuthorizationHandler<RecordOwnerRequirement>
{
private readonly ApplicationDbContext dbContext;
public RecordOwnerHandler(ApplicationDbContext dbContext)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordOwnerRequirement requirement)
{
if (IsUserAuthorized(context))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsUserAuthorized(AuthorizationHandlerContext context)
{
//****************************************
// Need the id here...
//****************************************
// Return the result
return true;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// *** Add policy for record owner ***
services.AddAuthorization(options =>
{
options.AddPolicy("RecordOwner", policy =>
policy.Requirements.Add(new RecordOwnerRequirement()));
});
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
// *** Register record owner handler with the DI container ***
services.AddTransient<IAuthorizationHandler, RecordOwnerHandler>();
services.AddMvc();
}
I tried using the IHttpContextAccessor
as a constructor parameter of RecordOwnerHandler
, but IHttpContextAccessor.HttpContext
doesn't seem to contain the RouteData
of the request.
I did several Google searches to see if there was any info about how to do this and came up blank.
Then I dug through the source code for both Routing and Model Binding, but can't seem to find an abstraction that is meant for injecting route values into services.
I realize I could try to parse this info out of the URL, but I am hoping for a cleaner way to get the value.
So, how can I access route values and/or value provider data inside of a service in ASP.NET Core 2.0?
Route values can be accessed by using the ActionContextAccessor
class.
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
public class RecordOwnerHandler : AuthorizationHandler<RecordOwnerRequirement>
{
private readonly ApplicationDbContext dbContext;
private readonly IActionContextAccessor actionContextAccessor;
public RecordOwnerHandler(ApplicationDbContext dbContext, IActionContextAccessor actionContextAccessor)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
this.actionContextAccessor = actionContextAccessor ?? throw new ArgumentNullException(nameof(actionContextAccessor));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordOwnerRequirement requirement)
{
if (IsUserAuthorized(context))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsUserAuthorized(AuthorizationHandlerContext context)
{
// Now the id route value can be accessed directly...
var id = this.actionContextAccessor.ActionContext.RouteData.Values["id"];
// Use the dbContext to compare the id against the database...
// Return the result
return true;
}
}
NOTE: I would still like to find out a way to access the value providers to do this, so it wouldn't matter if the parameter is passed through route values, query string, form values, etc.
For future reference, starting .NET Core 5.0, the HttpContext is now passed instead, so you can do:
if (context.Resource is HttpContext httpContext)
{
var value = httpContext.GetRouteValue("key");
}
See this for reference [AspNetCore] Authorization resource in endpoint routing will now be HttpContext
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With