We're implementing a new web application in Asp.net core 2.0, and I'd like to be able to restrict actions based on a combination of things, rather than one particular user role (like admin, power user, etc). The problem space looks like this:
Currently, we use a home-grown solution that's getting out of hand to limit permissions, but it only works with a users 'home' facility. We can't grant someone that orders inventory from another facility, for example, to view a different facility's inventory.
We've attempted to just apply roles for each action in each facility (Yikes!) that are generated on the fly, but this lead to some users getting permissions they shouldn't have. Not to mention, its a nightmare to maintain.
How can I extend the Roles Functionality in ASP.NET Core 2.0 to allow my users to have different permissions in different facilities without having to create roles for each action at each facility?
I'd recommend using policies. They give you much finer-grained control. Basically, you start with one or more "requirements". For example, you might start with something like:
public class ViewFacilitiesRequirement : IAuthorizationRequirement
{
}
public class OrderFacilitiesRequirement : IAuthorizationRequirement
{
}
These mostly function as attachments for authorization handlers, so they're pretty basic. The meat comes in those authorization handlers, where you define what meeting the requirement actually means.
public class ViewFacilitiesHandler : AuthorizationHandler<ViewFacilitiesRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewFacilitiesRequirement requirement)
{
// logic here, if user is authorized:
context.Succeed(requirement);
}
}
Authorization handlers are dependency injected, so you can inject things like your DbContext
, UserManager<TUser>
, etc. into them in the normal way and then query those sources to determine whether or not the user is authorized.
Once you've got some requirements and handlers, you need to register them:
services.AddAuthorization(o =>
{
o.AddPolicy("ViewFacilities", p =>
p.Requirements.Add(new ViewFacilitiesRequirement()));
});
services.AddScoped<IAuthorizationHandler, ViewFacilitiesHandler>();
In case it's not obvious, a policy can utilize multiple requirements. All will have to pass for the policy to pass. The handlers just need to be registered with the DI container. They are applied automatically based on the type(s) of requirements they apply to.
Then, on the controller or action that needs this permission:
[Authorize(Policy = "ViewFacilities")]
This is a very basic example, of course. You can make handlers than can work with multiple different requirements. You can build out your requirements a bit more, so you don't need as many of those either. Or you may prefer to be more explicit, and have requirements/handlers for each specific scenario. It's entirely up to you.
For more detail, see the documentation.