Search code examples
c#identityserver4claims

Identity Server 4 add custom claims to User


I have set up Identity Server 4 (a while back, so I have forgotten a lot of things) and I am trying to set claims on the current logged in User. When I login and use a filter to see what claims they have I can see this:

enter image description here

You can see from this image that my scopes, claims and roles have been added to the user which is good. But the claims have a type of Role which I think is wrong. I want to change that to Claim instead:

enter image description here

I have searched my application for ClaimTypes.Role and role but cannot find where I have set this up. Is this something that can be done easily?


Solution

  • I managed to sort this out; so basically I already had roles set up. I just didn't understand how I had done it in the past. The main thing to do was set up the ApiResource which I did like this:

    new ApiResource(IdentityConstants.ApiResources.IdentityServer, "Identity Server", new[] {JwtClaimTypes.Role, IdentityConstants.ClaimTypes.Permission})
    

    My constants class just has a few static strings set up like this:

    public static class SituIdentityConstants
    {
        public static class ApiResources
        {
            public const string Sxp = "sxp";
            public const string IdentityServer = "identity-server";
        }
    
        public static class ClaimTypes
        {
            public const string Permission = "permission";
        }
    }
    

    Once that was done; I updated my seed and included this:

    private static void CreateApiResources(ConfigurationDbContext context)
    {
        var scopes = ListApiScopes();
        var resources = ListApiResources();
    
        foreach (var scope in scopes)
            if (!context.ApiScopes.Any(m => m.Name.Equals(scope.Name)))
                context.ApiScopes.Add(scope.ToEntity());
    
        foreach (var resource in resources)
        {
            if (context.ApiResources.Any(m => m.Name.Equals(resource.Name))) continue;
    
            var entity = resource.ToEntity();
            entity.Scopes = scopes.Select(m => new ApiResourceScope
            {
                Scope = m.Name
            }).ToList();
            context.Add(entity);
        }
    
        context.SaveChanges();
    }
    

    Which created all the API Resources, scopes and any ApiResourceClaims (which is what populates RequestedClaimTypes in the IProfileService.

    Btw, for reference, here is my ProfileService:

    public class ProfileService: IProfileService
    {
        private readonly IMediator _mediator;
        private readonly UserManager<User> _userManager;
        private readonly RoleManager<Role> _roleManager;
        
        public ProfileService(IMediator mediator, UserManager<User> userManager, RoleManager<Role> roleManager)
        {
            _mediator = mediator;
            _userManager = userManager;
            _roleManager = roleManager;
        }
    
        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var user = await _userManager.FindByIdAsync(context.Subject.GetSubjectId());
            var rolesAttempt = await _mediator.Send(new ListRoles());
            if (rolesAttempt.Failure) return;
    
            var roles = rolesAttempt.Result;
            var issuedClaims = new List<System.Security.Claims.Claim>();
            
            foreach (var role in roles)
            {
                if (!user.Roles.Any(m => m.RoleId.Equals(role.Id))) continue;
    
                issuedClaims.Add(new System.Security.Claims.Claim(JwtClaimTypes.Role, role.Name));
    
                var roleClaims = await _roleManager.GetClaimsAsync(new Role {Id = role.Id});
                issuedClaims.AddRange(roleClaims.Where(m => context.RequestedClaimTypes.Any(x => x.Equals(m.Type))));
            }
            
            context.IssuedClaims = issuedClaims;
        }
    
        public async Task IsActiveAsync(IsActiveContext context)
        {
            var sub = context.Subject.GetSubjectId();
            var user = await _userManager.FindByIdAsync(sub);
            var active = (user != null && (!user.LockoutEnabled || user.LockoutEnd == null)) ||
                         (user != null && user.LockoutEnabled && user.LockoutEnd != null &&
                          DateTime.UtcNow > user.LockoutEnd);
    
            context.IsActive = active;
        }
    }
    

    I actually found this to be a pain to set up. I tried the way the documentation states:

    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        // Stores clients and resources
        .AddConfigurationStore(options => options.ConfigureDbContext = ConfigureDbContext)
        // Stores tokens, consents, codes, etc
        .AddOperationalStore(options => options.ConfigureDbContext = ConfigureDbContext)
        .AddProfileService<ProfileService>()
        .AddAspNetIdentity<User>();
    

    But this would not work for me; so instead I did it like this:

    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        // Stores clients and resources
        .AddConfigurationStore(options => options.ConfigureDbContext = ConfigureDbContext)
        // Stores tokens, consents, codes, etc
        .AddOperationalStore(options => options.ConfigureDbContext = ConfigureDbContext)
        .AddAspNetIdentity<User>();
    
    services.AddScoped(typeof(IProfileService), typeof(ProfileService));
    

    That's all I needed to do for Identity Server, the RequestedClaimTypes is now populated both with "role" and "permission". The only other thing I did, was when I created a RoleClaim, I set the claim type to "permission" and it works:

    {
        "nbf": 1611600995,
        "exp": 1611604595,
        "iss": "https://localhost:44362",
        "aud": [
            "sxp",
            "identity-server"
        ],
        "client_id": "client",
        "sub": "949cc454-d7c9-45db-9eae-59e72d3025c1",
        "auth_time": 1611600983,
        "idp": "local",
        "role": "User Manager",
        "permission": [
            "users:write",
            "user:read"
        ],
        "jti": "39C9F31958972704730DA65A8FCDAAEE",
        "iat": 1611600995,
        "scope": [
        "identity:read",
        "identity:write",
            "sxp:read",
            "sxp:write"
        ],
        "amr": [
        "pwd"
        ]
    }
    

    Noice.