'Role based authorization using Keycloak and .NET core
Having a few minor issues with role based authorization with dotnet core 2.2.3 and Keycloak 4.5.0.
In Keycloak, I've defined a role of 'tester' and a client role 'developer' with appropriate role mappings for an 'admin' user. After authenticating to Keycloak; if I look at the JWT in jwt.io, I can see the following:
{
"realm_access": {
"roles": [
"tester"
]
},
"resource_access": {
"template": {
"roles": [
"developer"
]
},
...
},
...
}
In .NET core, I've tried a bunch of things such as adding [Authorize(Roles = "tester")]
or [Authorize(Roles = "developer")]
to my controller method as well as using a policy based authorization where I check context.User.IsInRole("tester")
inside my AuthorizationHandler<TRequirement>
implementation.
If I set some breakpoints in the auth handler. When it gets hit, I can see the 'tester' and 'developer' roles listed as items under the context.user.Claims
IEnumerable as follows.
{realm_access: {"roles":["tester"]}}
{resource_access: {"template":{"roles":["developer"]}}}
So I should be able to successfully do the authorization in the auth handler by verifying the values for realm_access
and resource_access
in the context.user.Claims
collection, but this would require me to deserialize the claim values, which just seem to be JSON strings.
I'm thinking there has to be better way, or I'm not doing something quite right.
Solution 1:[1]
"AspNetCore.Authorization" expects roles in a claim (field) named "roles". And this claim must be an array of string (multivalued). You need to make some configuration on Keycloak side.
The 1st alternative:
You can change the existing role path.
Go to your Keycloak Admin Console > Client Scopes > roles > Mappers > client roles
- Change "Token Claim Name" as "roles"
- Multivalued: True
- Add to access token: True
The 2nd alternative:
If you don't want to touch the existing path, you can create a new Mapper to show the same roles at the root as well.
Go to your Keycloak Admin Console > Client Scopes > roles > Mappers > create
- Name: "root client roles" (or whatever you want)
- Mapper Type: "User Client Role"
- Multivalued: True
- Token Claim Name: "roles"
- Add to access token: True
Solution 2:[2]
The 4th alternative: read roles from JWT on ticket received event. Here is the the snipet:
options.Events.OnTicketReceived = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties!.GetTokens().ToList();
ClaimsIdentity claimsIdentity = (ClaimsIdentity) ctx.Principal!.Identity!;
foreach (AuthenticationToken t in tokens)
{
claimsIdentity.AddClaim(new Claim(t.Name, t.Value));
}
var access_token = claimsIdentity.FindFirst((claim) => claim.Type == "access_token")?.Value;
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(access_token);
JObject obj = JObject.Parse(jwtSecurityToken.Claims.First(c => c.Type == "resource_access").Value);
var roleAccess = obj.GetValue("your_client_id")!.ToObject<JObject>()!.GetValue("roles");
foreach (JToken role in roleAccess!)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role.ToString()));
}
return Task.CompletedTask;
};
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | denizkanmaz |
Solution 2 | ktary |