Search code examples
c#asp.net-coreasp.net-identity

.NET identity different permissions based on current customer


I have a requirement for an app to have different user permission based on the current "customer" that the user is operating on behalf of. A user can be connected to one ore more customers. A user must be able to switch the customer he wants to operate on and get the right permission based on the customer choice. This can be some kind of dropdown or something.

For example for user Joe:

  • For Customer X he has permissions Products.Create
  • For Customer Y he has permissions Products.View

Can this be implemented with .NET identity?

I am thinking on maybe creating some custom tables like.

[Table UserCustomerPermissions]

UserId, PermissionValue, CustomerId

and then implement a custom AuthorizationHandler which should check what permissionvalue the user has for the customer that is active right now. The active customer could come from the querystring like ?customerId=CustomerX or in Session or from database I suppose.

The authorization handler could look something like this

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { if(customIdStr != null)
{
    var currentUser = await _userManager.GetUserAsync(context.User);
    var customIdStr = httpContext.Request.Query["customerId"].FirstOrDefault();
    var userOperatorRoles = _dbContext.OperatorRoles
                .Where(u => u.OperatorId == currentUser.Id && u.roleId == requirement.Permission && u.customerId == customIdStr)
                .ToList();//.ToList();
            
    if (userOperatorRoles.Any())
    {
        context.Succeed(requirement);
        return;
    }
}

Then I could place [Authorize] attributes on my actions like

 [Authorize(Policy = "Permissions.Products.View")]

which should trigger the authorization handler.

I am not sure on the right way to go here. Any thoughts?


Solution

  • I acchived it next way:

    Create class that will containt rules for roles that you will use in Authorize attribute AuthRolesPolicy.cs

    public static class AuthRolesPolicy
    {
        public const string Admin = "ADMIN";
        public const string View = "USER";
    
        public static readonly string[] UserLevel = { Admin, User};
        public static readonly string[] AdminLevel = { Admin};
    }
    

    Because you Admin can see everything for User you will need next helperRoleRequirement .cs

    public class RoleRequirement : IAuthorizationRequirement
    {
        public string[] Roles { get; }
    
        public RoleRequirement(string[] roles)
        {
            Roles = roles;
        }
    }
    

    Now we ready to create our authorization handler MyAuthorizationHandler.cs

    public class MyAuthorizationHandler : AuthorizationHandler<RoleRequirement>, IAuthorizationRequirement
    {
        private readonly IYourService _databaseService;
    
        public PortalAuthorizationHandler(IYourService databaseService)
        {
            _databaseService = databaseService;
        }
    
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
        {
            try
            {
                // Read user identities from authentication step
                var userId = UserClaimsReader.GetUserId(context.User);
                if (!string.IsNullOrEmpty(userId))
                {
                    // Get user information from DB
                    var user = await _databaseService.ReadUserCreds(userId);
    
                    if (user != null)
                    {
                        // Check is user role valid based on Policy level
                        bool isRoleValid = requirement.Roles.Contains(user.Role);
    
                        if (isRoleValid)
                        {
                            context.Succeed(requirement);
                            return;
                        }
                    }
                }
            }
            catch (Exception ex)
            {                
            }
            
            context.Fail();
            return;
        }
    }
    

    Finally, we can update Program.cs

    ...
    builder.Services.AddScoped<IAuthorizationHandler, MyAuthorizationHandler>();
    ...
    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy(AuthRolesPolicy.Admin, policy => policy.Requirements.Add(new RoleRequirement(AuthRolesPolicy.AdminLevel)));
        options.AddPolicy(AuthRolesPolicy.User, policy => policy.Requirements.Add(new RoleRequirement(AuthRolesPolicy.UserLevel)));
    });
    ...
    
    

    Now you can use roles policis in api controllers, ex.[Authorize(Policy = AuthRolesPolicy.Admin)]