Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add/manage user claims at runtime in IdentityServer4

I am trying to use IdentityServer4 in a new project. I have seen in the PluralSight video 'Understanding ASP.NET Core Security' that IdentityServer4 can be used with claims based security to secure a web API. I have setup my IdentityServer4 as a separate project/solution.

I have also seen that you can add an IProfileService to add custom claims to the token which is returned by IdentityServer4.

One plan is to add new claims to users to grant them access to different parts of the api. However I can't figure out how to manage the claims of the users on the IdentityServer from the api project. I assume I should be making calls to IdentotyServer4 to add and remove a users claims?

Additionally is this a good approach in general, as I'm not sure allowing clients to add claims to the IdentityServer for their own internal security purposes makes sense - and could cause conflicts (eg multiple clients using the 'role' claim with value 'admin'). Perhaps I should be handling the security locally inside the api project and then just using the 'sub' claim to look them up?

Does anyone have a good approach for this?

Thanks

like image 291
Levitybot Avatar asked Apr 18 '17 10:04

Levitybot


1 Answers

Old question but still relevant. As leastprivilege said in the comments

claims are about identity - not permissions

This rings true, but identity can also entail what type of user it is (Admin, User, Manager, etc) which can be used to determine permissions in your API. Perhaps setting up user roles with specific permissions? Essentially you could also split up Roles between clients as well for more control if CLIENT1-Admin should not have same permissions as CLIENT2-Admin.

So pass your Roles as a claim in your IProfileService.

public class ProfileService : IProfileService
{
    private readonly Services.IUserService _userService;

    public ProfileService(Services.IUserService userService)
    {
        _userService = userService;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        try
        {
            switch (context.Client.ClientId)
            {
                //setup profile data for each different client
                case "CLIENT1":
                {
                    //sub is your userId.
                    var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");

                    if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
                    {
                        //get the actual user object from the database
                        var user = await _userService.GetUserAsync(long.Parse(userId.Value));

                        // issue the claims for the user
                        if (user != null)
                        {
                            var claims = GetCLIENT1Claims(user);

                            //add the claims
                            context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
                        }
                    }
                }
                break;
                case "CLIENT2":
                {
                    //...
                }
            }
        }
        catch (Exception ex) 
        {
            //log your exceptions
        }
    }

    // Gets all significant user claims that should be included
    private static Claim[] GetCLIENT1Claims(User user)
    {
        var claims = new List<Claim>
        {
            new Claim("user_id", user.UserId.ToString() ?? ""),
            new Claim(JwtClaimTypes.Name, user.Name),
            new Claim(JwtClaimTypes.Email, user.Email ?? ""),
            new Claim("some_other_claim", user.Some_Other_Info ?? "")
        };

        //----- THIS IS WHERE ROLES ARE ADDED ------
        //user roles which are just string[] = { "CLIENT1-Admin", "CLIENT1-User", .. }
        foreach (string role in user.Roles)
            claims.Add(new Claim(JwtClaimTypes.Role, role));

        return claims.ToArray();
    }
}

Then add [Authorize] attribute to you controllers for your specific permissions. This only allow specific roles to access them, hence setting up your own permissions.

[Authorize(Roles = "CLIENT1-Admin, CLIENT2-Admin, ...")]
public class ValuesController : Controller
{
    //...
}

These claims above can also be passed on authentication for example if you are using a ResourceOwner setup with custom ResourceOwnerPasswordValidator. You can just pass the claims the same way in the Validation method like so.

context.Result = new GrantValidationResult(
    subject: user.UserId.ToString(),
    authenticationMethod: "custom",
    claims: GetClaims(user));

So like leastprivilege said, you dont want to use IdentityServer for setting up permissions and passing that as claims (like who can edit what record), as they are way too specific and clutter the token, however setting up Roles that -

grant them access to different parts of the api.

This is perfectly fine with User roles.

Hope this helps.

like image 177
Nick De Beer Avatar answered Nov 02 '22 09:11

Nick De Beer