Reputation: 13895
I have setup a standard authentication system up in an ASP.NET Core application.
Users, Roles, RoleClaims(acting as permissions)
In Startup.cs I create a policy for each Role and each Permission. Assuming this would give me full flexibility in my Views to be able to say I want this button to show if user is part of a role that has claim DeleteCustomer or if User belongs to role Superuser.
How can I do an OR condition using the Authorize attribute. For example all throughout my site I want SuperuserRole Policy to have full permission to everything.
Over an action method let's say I have the following:
[Authorize(Policy = "EditCustomer")]
This will require that the logged in user is assigned to a role that has the claim: Edit.Customer since I am creating a policy for the claim Edit.Customer. This is all working fine, but how do I say I would like any User with the Role Superuser to be able to access EditCustomer action method. Superuser is in the database as a Role and additionally added as a policy called RequireSuperUser.
Upvotes: 8
Views: 14044
Reputation: 1184
We can inject DBContext (if using EF, or user repository if not) or HttpContextAccessor to the AuthorizationHandler
to achieve this.
By injecting EF Context (or user repository), we could query database to figure out if the current user is a superuser.
Or by injecting HttpContextAccessor
, we can determine if the user is a superuser from that. This approach is faster than the first one but depend on whether User Context have the information needed or not.
To achieve this, assuming what you have are EditCustomerRequirement
and EditCustomerAuthorizationHandler
:
public class EditCustomerAuthorizationHandler: AuthorizationHandler<EditCustomerRequirement>
{
readonly AppDbContext _context;
readonly IHttpContextAccessor _contextAccessor;
public EditCustomerAuthorizationHandler(DbContext c, IHttpContextAccessor ca)
{
_context = c;
_contextAccessor = ca;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, EditCustomerRequirement requirement)
{
//here we have the access to DB and HttpContext
//we can get the information if the user is superuser from either db or httpContext
var isSuperUser = await IsSuperUser(_context);
//or
var isSuperUser = IsSuperUser(_contextAccessor);
if(isSuperUser)
{
context.Succeed(requirement);
}
else
{
//the current implementation of EditCustomer Policy
}
}
}
IsSuperUser
is some kind of method that we could make to get the information from either db context or http context.
Upvotes: 0
Reputation: 8762
You can use a two different handlers of AuthorizationHandler
with the same requirement of IAuthorizationRequirement
:
public class FooOrBooRequirement : IAuthorizationRequirement { }
public class FooHandler : AuthorizationHandler<FooOrBooRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationContext context, FooOrBooRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "Foo" && c.Value == true))
{
context.Succeed(requirement);
return Task.FromResult(0);
}
}
}
public class BooHandler : AuthorizationHandler<FooOrBooRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationContext context, FooOrBooRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "Boo" && c.Value == true))
{
context.Succeed(requirement);
return Task.FromResult(0);
}
}
}
So in the AddPolicy
you will have only one requirement that can be fulfilled of by the FooHandler
or by the BooHandler
:
services.AddAuthorization(authorizationOptions =>
{
authorizationOptions.AddPolicy(
"MustBeFooOrBoo",
policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new FooOrBooRequirement());
});
});
Please note that the AuthorizationHandler
provides a more powerful custom policy so it most relevant only for special cases that require a more complex authentication, for example if you want to determine if the user is a resource owner.
For an only simple claims-based policy I'd advice to use the @richard-mneyan's answer.
Upvotes: 2
Reputation: 692
You can add OR condition in Startup.cs:
services.AddAuthorization(options => {
options.AddPolicy("EditCustomer", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c => (c.Type == "DeleteCustomer" || c.Type == "Superuser"))));
});
I was facing similar issue where I wanted only "John Doe", "Jane Doe" users to view "Ending Contracts" screen OR anyone only from "MIS" department also to be able to access the same screen. The below worked for me, where I have claim types "department" and "UserName":
services.AddAuthorization(options => {
options.AddPolicy("EndingContracts", policy =>
policy.RequireAssertion(context => context.User.HasClaim(c => (c.Type == "department" && c.Value == "MIS" ||
c.Type == "UserName" && "John Doe, Jane Doe".Contains(c.Value)))));
});
Upvotes: 10