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
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
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"
}
}
};
}