8

I am trying to secure my webAPI using asp.net Identity Core. Now I want to create Roles Dynamically and set and remove permission from/to them and in my admin panel.

for example, I have this permissions list:

  • register task
  • assign task
  • change task status
  • verify task status

now I want to create different roles and set this permission to them as my needs and assign these roles to each user.

I searched in UserManager and RoleManager of Identity framework but there was no way to create this functionality.

is there any method for implementing this functionality? I find this useful but this is about dotnet

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Navid_pdp11
  • 3,487
  • 3
  • 37
  • 65
  • Possible duplicate of [How to create roles in asp.net core and assign them to users](https://stackoverflow.com/questions/42471866/how-to-create-roles-in-asp-net-core-and-assign-them-to-users) – Ian Kemp Dec 30 '18 at 19:43
  • 4
    No this is not. I am searching for permission base authorization but in your link, it is about role-based authorization and specifically about creating roles. – Navid_pdp11 Dec 31 '18 at 05:34

2 Answers2

22

I find an approach which is using claim and policy for creating a permission-based authorization in this link.

I create a custom claim type such as Application.Permission and then create some classes as following to define my permissions:

public class CustomClaimTypes
{
    public const string Permission = "Application.Permission";
}

public static class UserPermissions
{
    public const string Add = "users.add";
    public const string Edit = "users.edit";
    public const string EditRole = "users.edit.role";
} 

and then I create My roles and then Assign these permissions as claims to the roles with key ApplicationPermission.

await roleManager.CreateAsync(new ApplicationRole("User"));
var userRole = await roleManager.FindByNameAsync("User");
await roleManager.AddClaimAsync(userRole, new Claim(CustomClaimTypes.Permission, Permissions.User.View));    
await roleManager.AddClaimAsync(userRole, new Claim(CustomClaimTypes.Permission, Permissions.Team.View));

in the next step, I add these claims to my token when the user is trying to login to system:

var roles = await _userManager.GetRolesAsync(user);
var userRoles = roles.Select(r => new Claim(ClaimTypes.Role, r)).ToArray();
var userClaims = await _userManager.GetClaimsAsync(user).ConfigureAwait(false);
var roleClaims = await GetRoleClaimsAsync(roles).ConfigureAwait(false);
var claims = new[]
             {
                 new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                 new Claim(ClaimTypes.Email, user.Email),
                 new Claim(ClaimTypes.Name, user.UserName)
             }.Union(userClaims).Union(roleClaims).Union(userRoles);

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SigningKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
    issuer: _jwtSettings.Issuer,
    audience: _jwtSettings.Audience,
    claims: claims,
    expires: DateTime.UtcNow.AddYears(1),
    signingCredentials: creds);

then I create my policies this way:

public static class PolicyTypes
{
    public static class Users
    {
        public const string Manage = "users.manage.policy";
        public const string EditRole = "users.edit.role.policy";
    }
}

then I set up my authorization service inside startup.cs file in ConfigureServiceSection:

services.AddAuthorization(options =>
{
    options.AddPolicy(PolicyTypes.Users.Manage, policy => { policy.RequireClaim(CustomClaimTypes.Permission, Permissions.Users.Add); });
    options.AddPolicy(PolicyTypes.Users.EditRole, policy => { policy.RequireClaim(CustomClaimTypes.Permission, Permissions.Users.EditRole); });
}

finally, I set policies on my routes and finish:

[Authorize(Policy = PolicyTypes.Users.Manage)]
public async Task<IEnumerable<TeamDto>> GetSubTeams(int parentId)
{
    var teams = await _teamService.GetSubTeamsAsync(parentId);
    return teams;
}
Navid_pdp11
  • 3,487
  • 3
  • 37
  • 65
  • 1
    How would you solve the cookie size limit issue? There is at least 50 character per permission, with size limit as low as 4K the app would run out of space very soon... unless it would fall back to the role system of course. – wondra Apr 14 '20 at 12:39
  • My case was a WebAPI and I did not use cookies. also, our permissions are Carried by JWT-token between server and client and it can save anywhere that front end preferred. – Navid_pdp11 Apr 15 '20 at 06:19
  • Sorry - meant the HTTP header size, not a cookie. I take it you never needed more than a dozen or two permissions then, thanks for the implementation clarification. – wondra Apr 15 '20 at 08:53
  • I use JWT (Authorization Header) based on this SO https://stackoverflow.com/a/26034157/1093228 it is not easy to pass the limitation. but we had a maximum of 40 permission per user and it was okay. – Navid_pdp11 Apr 16 '20 at 06:34
  • What if someone changes permissions for the role? You would need invalidate all tokens that are affected by this change? Have you encountered this problem? – Štefan Bartoš May 11 '20 at 06:06
  • 1
    yes! of course! we create an in-memory blacklist for all users their role is changed. and also decrease or JWT token lifetime to a shorter period and implement a refresh token mechanism. – Navid_pdp11 May 11 '20 at 08:49
  • Yes this is just a helper method to retraive Claims of Roles those are assigned to current user – Navid_pdp11 Nov 20 '20 at 12:59
  • What is the advantage of doing it via the role and not the user say we want to restric jane can only view a screen but not edit this wouldnt work on this scenario. – c-sharp-and-swiftui-devni May 14 '21 at 18:57
  • its advantage is using claims in jwt and it make or api faster to evaluation the permissions of user. I did not understood second part f comment. – Navid_pdp11 May 14 '21 at 20:27
  • @Navid_pdp11 in my case `GetRoleClaimsAsync` does not work and gives me the error `GetRoleClaimsAsync does not exist in the current context` what is the problem??? – abdul wahid Nov 30 '22 at 07:50
  • GetRoleClaimes is a custom function to retrieve the claims assigned to the given role. – Navid_pdp11 Nov 30 '22 at 21:12
  • @Navid_pdp11 oh I thought it's a built-in Function from ASP.NET Core, Now I understand and I will try to create this custom function for my project. – abdul wahid Dec 01 '22 at 04:31
  • @Navid_pdp11 if possible please put a sample of the GetRoleClaimsAsync function code here Thanks – abdul wahid Dec 01 '22 at 05:45
1

Another solution could be to use funcs to fullfill a policy, see this example taken from https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-3.1:

services.AddAuthorization(options =>
{
     options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                (c.Type == "BadgeId" ||
                 c.Type == "TemporaryBadgeId") &&
                 c.Issuer == "https://microsoftsecurity")));
});

With this in mind you could make something like the following.

First a class for all of your individual permissions:

public static class PrincipalPermission{

       public static List<Func<AuthorizationHandlerContext, bool>> Criteria = new List<Func<AuthorizationHandlerContext, bool>>
        {
            CanCreateUser
        };

        public static bool CanCreateUser(this AuthorizationHandlerContext ctx)
        {
            return ctx.User.IsInRole(RoleEnum.Admin.ToString());
        }
}

Then this should be added to your configuration:

services.AddAuthorization(options =>
{

    foreach (var criterion in PrincipalPermissions.Criteria)
            {
                options.AddPolicy(criterion.Method.Name, 
                                  policy => policy.RequireAssertion(criterion));
            }  
}

now this can be added to your controller:

    [Authorize(Policy = nameof(PrincipalPermissions.CanCreateUser))]
    public async Task<UserDto> Create([FromBody] CreateUserCommand cmd)
    {
       return await HandleCreateUser(cmd);
    }

By the way, if anyone thinks it is annoying to add the methods to a list of Criteria i am right there with you, and i would appreciate if anyone could come up with a better solution :)