Payam Khaninejad
Payam Khaninejad

Reputation: 7996

IdentityServer4 Role Based Authorization on multiple roles

I want to implement multiple role-based authorizations with IdentityServer4 hybrid, everything is fine but when I want to use like this:

[Authorize(Roles = "Admin,SalaryUser")]

it doesn't allow me and give access denied.

In my scenario, users have multiple roles and if a role is valid the controller should give me access, for example in the above code the controller should give access to these users: the users have the SalaryUser role, the users with admin roles, the users have both Admin, SalaryUser roles.

here is configuration:

.AddOpenIdConnect("oidc", options =>
                    {
                        options.SignInScheme = "Cookies";

                        options.Authority = authority;
                        options.RequireHttpsMetadata = false;
                        options.ClientId = clientId;
                        options.ClientSecret = "secret";
                        options.ResponseType = "code id_token";
                        options.UseTokenLifetime = false;
                        options.SaveTokens = true;
                        options.GetClaimsFromUserInfoEndpoint = true;





                        options.ClaimActions.MapCustomJson("role", jobj =>
                        {
                            IEnumerable<string> values = jobj["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"].Values<string>();
                            StringBuilder sb = new StringBuilder();
                            foreach (string val in values)
                            {
                                sb.Append(val + ",");
                            }
                            return sb.ToString().TrimEnd(',');
                        });

                        options.Scope.Add("api1");
                        options.Scope.Add("offline_access");
                       // options.Scope.Add("roles");

                        options.Events = new OpenIdConnectEvents()
                        {

                            OnUserInformationReceived = async UserInformationReceivedContext =>
                            {
                                // UserInformationReceivedContext.User.Remove("address");

                                if (UserInformationReceivedContext.User.TryGetValue("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", out JToken role))
                                {
                                    var claims = new List<Claim>();
                                    if (role.Type != JTokenType.Array)
                                    {
                                        claims.Add(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", (string)role));
                                    }
                                    else
                                    {
                                        foreach (var r in role)
                                            claims.Add(new Claim("role", (string)r));
                                    }
                                    var id = UserInformationReceivedContext.Principal.Identity as ClaimsIdentity;
                                    id.AddClaims(claims);
                                }
                            }
                        };

                        options.ClaimActions.MapAll();
                    });

Upvotes: 1

Views: 2107

Answers (2)

Nan Yu
Nan Yu

Reputation: 27528

You don't need to manually map claims with MapCustomJson or in your OnUserInformationReceived .

If the claim in jwt token is role not http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role , you can set client app's role validation claim :

options.TokenValidationParameters = new TokenValidationParameters
{
    RoleClaimType = "role"
};

Another way is when adding role claim to your issued token on identity server application , use ClaimTypes.Role :

new Claim(ClaimTypes.Role, "Admin"),

Upvotes: 1

Dennis VW
Dennis VW

Reputation: 3177

You need to map the JWT claim type to the claim type that Identity uses.

The issue at hand is that the identity system for ASP.NET Core relies on the ClaimTypes.Role constant (http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role) to determine the roles of the user. However, the name of the claim that corresponds to the role on an OpenID JWT token is simply role. Any time you need to merge OIDC identity and ASP.NET identity, you have to translate claims like this.

I rewrote your code a little bit because you can work with the claims identity without using the JTokenType. Although I didn't have time to test this thoroughly so if it doesn't then stick to your code but replace "role" with ClaimTypes.Role when you add claims to the identity.

            OpenIdConnectEvents CreateOpenIdConnectEvents()
            {
                return new OpenIdConnectEvents()
                {
                    OnTicketReceived = context =>
                    {
                        var identity = context.Principal.Identity as ClaimsIdentity;
                        if (identity != null)
                        {
                            if (identity.HasClaim(c => c.Type == "role"))
                            {
                                foreach (var role in identity.Claims.Where(c => c.Type == "role"))
                                {
                                    if (!context.Principal.HasClaim(c => c.Type == ClaimTypes.Role && c.Value == role.Value))
                                    {
                                        identity.AddClaim(new Claim(ClaimTypes.Role, role.Value));
                                    }
                                }
                            }

                        }
                        return Task.FromResult(0);
                    }
                };
            }

Use like this:

options.Events = new CreateOpenIdConnectEvents()

Upvotes: 0

Related Questions