How to add roles to claims in IdentityServer4?

Slightly different question, absolutely matching answer.

With the Edit 1, IdP configuration looks enough to supply both identity and access tokens with roles when requested. The only thing left is to configure the client to request the access token (.Net client doesn't do that by default), or just request the roles scope within the identity token.

To get the roles with id_token, the client side config must include options.Scope.Add("roles");

To get the roles with bearer token, that token must be requested by specifying options.ResponseType = "id_token token"; in client side config.


I did it like this in .NET 5:

Add JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); before services.AddAuthentication in Startup.cs.

https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1349

I also added

services.AddScoped<IProfileService, ProfileService>();

and ProfileService.cs that looks like this to map roles to claims:

public sealed class ProfileService : IProfileService
{
    private readonly IUserClaimsPrincipalFactory<ApplicationUser> _userClaimsPrincipalFactory;
    private readonly UserManager<ApplicationUser> _userMgr;
    private readonly RoleManager<IdentityRole> _roleMgr;

    public ProfileService(
        UserManager<ApplicationUser> userMgr,
        RoleManager<IdentityRole> roleMgr,
        IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory)
    {
        _userMgr = userMgr;
        _roleMgr = roleMgr;
        _userClaimsPrincipalFactory = userClaimsPrincipalFactory;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        string sub = context.Subject.GetSubjectId();
        ApplicationUser user = await _userMgr.FindByIdAsync(sub);
        ClaimsPrincipal userClaims = await _userClaimsPrincipalFactory.CreateAsync(user);

        List<Claim> claims = userClaims.Claims.ToList();
        claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();

        if (_userMgr.SupportsUserRole)
        {
            IList<string> roles = await _userMgr.GetRolesAsync(user);
            foreach (var roleName in roles)
            {
                claims.Add(new Claim(JwtClaimTypes.Role, roleName));
                if (_roleMgr.SupportsRoleClaims)
                {
                    IdentityRole role = await _roleMgr.FindByNameAsync(roleName);
                    if (role != null)
                    {
                        claims.AddRange(await _roleMgr.GetClaimsAsync(role));
                    }
                }
            }
        }

        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        string sub = context.Subject.GetSubjectId();
        ApplicationUser user = await _userMgr.FindByIdAsync(sub);
        context.IsActive = user != null;
    }
}

Source:

https://ffimnsr.medium.com/adding-identity-roles-to-identity-server-4-in-net-core-3-1-d42b64ff6675


Two things you need to do to make sure you will get users roles in the claims:

1- In IdentityServer4 project: you need to have implementation for IProfileService http://docs.identityserver.io/en/latest/reference/profileservice.html

don't forget to add the class in startup.cs file like this

services.AddIdentityServer()
// I just removed some other configurations for clarity
                **.AddProfileService<IdentityProfileService>();**

2- In Web Client project's startup.cs file: when configuring the openId, you have to mention this :

services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";
            options.Authority = "Identity URL ";
            options.RequireHttpsMetadata = true;

            options.ClientId = "saas_crm_webclient";
            options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
            options.ResponseType = "code id_token";
            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = false;

            options.Scope.Add("test.api");
            options.Scope.Add("identity.api");
            options.Scope.Add("offline_access");


            **options.ClaimActions.Add(new JsonKeyClaimAction("role", null, "role"));**

            **options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
            {
                NameClaimType = "name",
                RoleClaimType = "role"
            };**
        });