Reputation: 948
I am fairly new with asp.net identity server. I know how to customize the IdentityUser entity and scaffold / override Identity UI. I'm looking for a high level how-to, best practice on authorization given these circumstances:
A component within the application needs to consider these items for authorization:
Coming from a class web forms background, I tend to think like so to define roles:
public int assignId; //key
public int userId;
public int roleId;
public int? locationId;
public int? divisionId;
public int? entityId;
What would be the best way to handle these sort of authorization rules / policies using asp.net core 2.1 identity? Adding claims like "loc"/"indy"?
Would I setup custom authorization handlers to check claims? Or would I set up a middleware table to handle the associations like I would back in the traditional webforms days? Or is there a best practice for this scenario??
Thank you for your help!!!!!!!
Upvotes: 2
Views: 1784
Reputation: 25360
Earlier verions of ASP.NET used a Role-Based approach . However , in this case the new Claims-Based approach is prefered . Because we can hardly determine which role is allowed to access the resource .
Let's say the three users and claims you described above :
Adam
: Role= Global AdminJoe
: Role=Admin; Location=Indy&Location=Chicago ; Division=Safety; Entity=NewsBlow
: Admin ; Location = Indy ; Division = IT ;the AspNetUserClaims
table records the claims of users as bleow :
ID | UserID | ClaimType | ClaimValue |
----|:------|:----------|:----------:|
3 | 3ff3d2db-5a8f-4b01-99b5-fe46d22c240c | Role | Global Admin
4 | cea5d395-fd46-4e6a-aa81-2f4c011b74be | Role | Admin
5 | cea5d395-fd46-4e6a-aa81-2f4c011b74be | Location | Indy
6 | cea5d395-fd46-4e6a-aa81-2f4c011b74be | Location | Chicago
8 | cea5d395-fd46-4e6a-aa81-2f4c011b74be | Division | Safety
9 | cea5d395-fd46-4e6a-aa81-2f4c011b74be | Entity | News
10 | b60c7b75-e31b-4856-ba98-666d013c8201 | Role | Admin
11 | b60c7b75-e31b-4856-ba98-666d013c8201 | Location | Indy
15 | b60c7b75-e31b-4856-ba98-666d013c8201 | Division | IT
As you see , the records of Claim-Based approach is rather simple and clean .When there's a need to authorize user , we can compare the user's claims with a policy :
services.AddAuthorization(opts=> {
// ... other policy ...
// ...
opts.AddPolicy("Check:Role|Location|Division|Entity", pb=>
pb.RequireAssertion(async context=> await RldeChecker.Handle(context) )
);
})
the Checker.Handle(context) here is a simple static method which receives an instance of AuthorizationHandlerContex as parameter and check if a user can access some specific resource .
To make it more clear , we can add a PolicyChecker/ folder , and place the RldeChecker
class into it :
public class RldeChecker
{
// ...
public static async Task<bool> Handle(AuthorizationHandlerContext context) {
var user = context.User;
// bypass all checks
if (user.HasClaim("Role","Global Admin" )) { return true; }
try
{
// retrieve the user claims
var userLocation = user.FindFirst("Location")?.Value;
var userDivision = user.FindFirst("Division")?.Value;
var userEntity = user.FindFirst("Entity")?.Value;
// retrieve the resource that the user want to access at runtime
var resource = (Dictionary<string, string>)context.Resource;
var targetLocation = resource["Location"];
var targetDivision = resource["Division"];
var targetEntity = resource["Entity"];
// check for local admin
// ...
}
catch {
return false;
}
return false;
}
}
when we want to authorize the user within an action method , we can simply inject an instance of IAuthorizationService
to check by authService.Authorize(user,resource,"Check:Role|Location|Division|Entity")
. In addition , the claim-Based approach allow us to use it in a service way and then we can inject it everywhere as we need , for example , displaying different contents according to current user's Location/Division/Entity :
var resource = new Dictionary<string, string>() {
{ "Location","Indy"},
{ "Division","IT"},
{ "Entity","News"},
};
var x = await this._authorizationService.AuthorizeAsync(User, resource, "Check:Role|Location|Division|Entity");
if (x.Succeeded)
{
return View();
}
else
{
return new ForbidResult();
}
Upvotes: 5