Fraze
Fraze

Reputation: 948

best practice: customize asp.net core identity authorization

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;

Real World Scenarios

So, my question?

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

Answers (1)

itminus
itminus

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 Admin
  • Joe : Role=Admin; Location=Indy&Location=Chicago ; Division=Safety; Entity=News
  • Blow : 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

Related Questions