Search code examples
c#asp.net-mvcidentityserver4

IdentityServer4. Using 2 roles per user


I'm using IdentityServer4 and an MVC client. My problem is just being able to use more than 1 role per user. In the example below, I have 2 controllers, each with its authorize. If 1 user has 2 Roles in the register, he gives access denied to both.

When you leave only 1 role for the user, it works just fine

Config.cs (IDS4)

        public static IEnumerable<IdentityResource> Ids =>
        new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResources.Phone(),
            new IdentityResource("role", "role", new List<string> { "role" }),
        };

            new Client
            {
                ClientId = "mvc",
                ClientName = "MVC App",
                ClientSecrets = { new Secret("secret".Sha256()) },
                AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                RequireConsent = false,

                RedirectUris = { mvcURL + "/signin-oidc" },
                FrontChannelLogoutUri = mvcURL + "/signout-oidc",
                PostLogoutRedirectUris = { mvcURL + "/signout-callback-oidc" },

                //AllowOfflineAccess = true,
                AllowedScopes = {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.Email,
                    IdentityServerConstants.StandardScopes.Phone,
                    "api", "role"
                }
            },

ProfileService.cs (IDS4)

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var sub = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(sub);
        var principal = await _claimsFactory.CreateAsync(user);

        var claims = principal.Claims.ToList();
        claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();

        // Add custom claims in token here based on user properties or any other source
        claims.Add(new Claim("id_user", user.Id.ToString()));
        claims.Add(new Claim("name", user.Name));
        claims.Add(new Claim("email", user.Email.ToString()));
        claims.Add(new Claim("role", user.role.ToString()));

        if (_userManager.SupportsUserRole)
        {
            var roles = await _userManager.GetRolesAsync(user);
            foreach (var roleName in roles)
            {
                claims.Add(new Claim(JwtClaimTypes.Role, roleName));

                if (_roleManager.SupportsRoleClaims)
                {
                    var role = await _roleManager.FindByNameAsync(roleName);
                    if (role != null)
                        claims.AddRange(await _roleManager.GetClaimsAsync(role));
                }
            }
        }

        context.IssuedClaims = claims;
    }

Startup.cs (MVC)

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;

        })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
            {
                options.Authority = Configuration.GetSection("ids_URI").Value;
                options.ClientId = "mvc";
                options.ClientSecret = "secret";
                options.ResponseType = OpenIdConnectResponseType.Code;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("email");
                options.Scope.Add("role");
                options.SaveTokens = true;
                options.ClaimActions.DeleteClaim("sid");
                options.ClaimActions.DeleteClaim("idp");
                options.ClaimActions.MapUniqueJsonKey("role", "role", "role");
                options.TokenValidationParameters.RoleClaimType = "role";
            });

Controller 1 (MVC)

[Authorize(Roles = "CANCELAMENTO")]
public class CancelamentoController : Controller

Controller 2 (MVC)

[Authorize(Roles = "ABASTECIMENTO")]
public class AbastecimentoController : Controller

In the AspNetUserRoles table I have 2 roles for the user

inserir a descrição da imagem aqui

The User object is coming from the roles:

Claims [IEnumerable]:{System.Security.Claims.ClaimsPrincipal.<get_Claims>d__22}
 Membros não públicos
Visualização dos Resultados:Expandir a Visualização dos Resultados vai enumerar o IEnumerable
   [0] [Claim]:{s_hash: 6WmMG-GghFBT9LCigeOnnw}
   [1] [Claim]:{sub: 64637127-9e46-46e2-ad51-47297a52d483}
   [2] [Claim]:{auth_time: 1641251760}
   [3] [Claim]:{amr: pwd}
   [4] [Claim]:{name: NOME}  
   [5] [Claim]:{given_name: SOBRENOME}
   [6] [Claim]:{email: [email protected]}
   [8] [Claim]:{role: ["ABASTECIMENTO","CANCELAMENTO"]}

Any idea?

Thank you


Solution

  • I had the same issue and I changed

    options.ClaimActions.MapUniqueJsonKey("role", "role", "role");
    

    to

    options.ClaimActions.MapJsonKey("role", "role", "role");
    

    The User object will be like:

    [8] [Claim]:{role: "ABASTECIMENTO"}
    [9] [Claim]:{role: "CANCELAMENTO"}
    

    With this approach User.IsInRole("role name"); works fine.

    My StartUp.cs in MVC:

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {
        options.Authority = "https://localhost:5001";
        options.ClientId = "mvc";
        options.ClientSecret = "secret";
        options.ResponseType = "code id_token";
        options.Scope.Add("email");
        options.Scope.Add("roles");
    
        options.ClaimActions.DeleteClaim("sid");
        options.ClaimActions.DeleteClaim("idp");
        options.ClaimActions.DeleteClaim("s_hash");
        options.ClaimActions.DeleteClaim("auth_time");
        options.ClaimActions.MapJsonKey("role", "role");
        options.Scope.Add("api1");
        options.SaveTokens = true;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };
    });
    

    Config.cs in IDS4:

    public static class Config
    {
        public static IEnumerable<IdentityResource> IdentityResources =>
            new List<IdentityResource>
            {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResource("roles", "Your role(s)", new List<string>() { "role" })
            };
    
    
        public static IEnumerable<ApiScope> ApiScopes =>
            new List<ApiScope>
            {
                new ApiScope("api1", "My API")
            };
    
        public static IEnumerable<Client> Clients =>
            new List<Client>
            {
            new Client
            {
                ClientId = "mvc",
                ClientName = "Application Web",
                AllowedGrantTypes = GrantTypes.Hybrid,
                ClientSecrets = { new Secret("secret".Sha256()) },
                RequirePkce = false,
                AllowRememberConsent = false,
                RedirectUris = { "https://localhost:5003/signin-oidc" },
    
                PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },
    
                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.Email,
                    "api1",
                    "roles"
                }
            }
            };
    }