Description
openedon Nov 7, 2024
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
I am using the Blazor 8 BlazorWebAppOidc sample to authenticate and authorize using OpenIdConnect with keycloak. I am seeing an issue where role claims from the userinfo endpoint do not propogate to the client. My setup is as follows:
builder.Services.AddAuthentication(MS_OIDC_SCHEME)
.AddOpenIdConnect(MS_OIDC_SCHEME, oidcOptions =>
{
oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
oidcOptions.Authority = "https://host.docker.internal/keycloak/realms/Autostore/";
oidcOptions.ClientId = "WMSServiceCalendar";
oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtRegisteredClaimNames.Name,
RoleClaimType = "role"
};
oidcOptions.UsePkce = true;
oidcOptions.GetClaimsFromUserInfoEndpoint = true;
oidcOptions.Events.OnUserInformationReceived = ctx =>
{
Console.WriteLine();
Console.WriteLine("Claims from the ID token");
foreach (var claim in ctx.Principal.Claims)
{
Console.WriteLine($"{claim.Type} - {claim.Value}");
}
Console.WriteLine();
Console.WriteLine("Claims from the UserInfo endpoint");
foreach (var property in ctx.User.RootElement.EnumerateObject())
{
Console.WriteLine($"{property.Name} - {property.Value}");
}
return Task.CompletedTask;
};
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Events.OnSigningIn = ctx =>
{
Console.WriteLine();
Console.WriteLine("Claims received by the Cookie handler");
foreach (var claim in ctx.Principal.Claims)
{
Console.WriteLine($"{claim.Type} - {claim.Value}");
}
Console.WriteLine();
return Task.CompletedTask;
};
});
With the OnUserInformationReceived logging I can see the claims coming from Keycloak:
Claims from the ID token
exp - 1730968528
iat - 1730968228
auth_time - 1730968228
jti - 1801bcab-dfd5-463f-9bf6-7cf84ded884a
iss - https://host.docker.internal/keycloak/realms/Autostore
aud - WMSServiceCalendar
sub - c5f3046c-b1b7-497d-b12e-a3f29afc5d11
typ - ID
azp - WMSServiceCalendar
nonce - 638665650239925001.M2YxOGZlNDMtMzkxYi00MDhkLWJmZTEtYzM1Y2QwNjI3NjAxODBkODhmZDAtMjE3OC00ZDYyLWEwZjktZGM0MjI5MzZkNjA1
sid - 654b19df-4dcf-405b-9d34-27bffd126968
at_hash - iCCj0tkd2HpoJZRclzph-w
acr - 1
email_verified - true
name - manager name
preferred_username - manager
given_name - manager
family_name - name
Claims from the UserInfo endpoint
sub - c5f3046c-b1b7-497d-b12e-a3f29afc5d11
resource_access - {"CommonClient":{"roles":["CommonClient:GrafanaViewer","CommonClient:MessagingAdmin","CommonClient:GrafanaEditor","CommonClient:GrafanaAdmin","CommonClient:UserManagement","CommonClient:Users"]}}
email_verified - True
role - ["WMSServiceCalendar:VehicleTypeDelete","WMSServiceCalendar:VehicleDelete","WMSServiceCalendar:VehicleExport","WMSServiceCalendar:VehicleTypeView","WMSServiceCalendar:VehicleView","WMSServiceCalendar:Users"]
name - manager name
preferred_username - manager
given_name - manager
family_name - name
Claims received by the Cookie handler
auth_time - 1730968228
jti - 1801bcab-dfd5-463f-9bf6-7cf84ded884a
sub - c5f3046c-b1b7-497d-b12e-a3f29afc5d11
typ - ID
sid - 654b19df-4dcf-405b-9d34-27bffd126968
email_verified - true
name - manager name
preferred_username - manager
given_name - manager
family_name - name
role - WMSServiceCalendar:VehicleTypeDelete
role - WMSServiceCalendar:VehicleDelete
role - WMSServiceCalendar:VehicleExport
role - WMSServiceCalendar:VehicleTypeView
role - WMSServiceCalendar:VehicleView
role - WMSServiceCalendar:Users
I have amended UserInfo as follows:
public sealed class UserInfo
{
public required string UserId { get; init; }
public required string Name { get; init; }
public required string[] Roles { get; init; }
public const string UserIdClaimType = "sub";
public const string NameClaimType = "name";
public const string RoleClaimType = "role";
public static UserInfo FromClaimsPrincipal(ClaimsPrincipal principal) =>
new()
{
UserId = GetRequiredClaim(principal, UserIdClaimType),
Name = GetRequiredClaim(principal, NameClaimType),
Roles = principal.FindAll(RoleClaimType).Select(c => c.Value).ToArray(),
};
public ClaimsPrincipal ToClaimsPrincipal() =>
new(new ClaimsIdentity(
Roles.Select(role => new Claim(RoleClaimType, role))
.Concat([
new Claim(UserIdClaimType, UserId),
new Claim(NameClaimType, Name),
]),
authenticationType: nameof(UserInfo),
nameType: NameClaimType,
roleType: RoleClaimType));
private static string GetRequiredClaim(ClaimsPrincipal principal, string claimType) =>
principal.FindFirst(claimType)?.Value ?? throw new InvalidOperationException($"Could not find required '{claimType}' claim.");
}
In my client I then have the following in my page:
@attribute [Authorize(Roles = "WMSServiceCalendar:Users")]
As a result of the issue I see the following error:
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: RolesAuthorizationRequirement:User.IsInRole must be true for one of the following roles: (WMSServiceCalendar:Users)
Note: If I add the claims to the id token in keycloak then all works but that feels like it defeats the point of using GetClaimsFromUserInfoEndpoint?
Expected Behavior
Using GetClaimsFromUserInfoEndpoint = true should flow the claims from server to client side and allow the roles to be used during authorization
Steps To Reproduce
I am using the above setup with no further modifications to the sample application.
Exceptions (if any)
No response
.NET Version
8.0.403
Anything else?
ID Token:
{
"exp": 1730967440,
"iat": 1730967140,
"jti": "aab08d01-1d37-4d87-9c73-7f9f95aa103c",
"iss": "https://host.docker.internal/keycloak/realms/Autostore",
"aud": "WMSServiceCalendar",
"sub": "c5f3046c-b1b7-497d-b12e-a3f29afc5d11",
"typ": "ID",
"azp": "WMSServiceCalendar",
"sid": "0bf27112-0271-4ce1-9d97-9e1d18cc9214",
"acr": "1",
"email_verified": true,
"name": "manager name",
"preferred_username": "manager",
"given_name": "manager",
"family_name": "name"
}
User Info:
{
"sub": "c5f3046c-b1b7-497d-b12e-a3f29afc5d11",
"email_verified": true,
"role": [
"WMSServiceCalendar:VehicleTypeDelete",
"WMSServiceCalendar:VehicleDelete",
"WMSServiceCalendar:VehicleExport",
"WMSServiceCalendar:VehicleTypeView",
"WMSServiceCalendar:VehicleView",
"WMSServiceCalendar:Users"
],
"name": "manager name",
"preferred_username": "manager",
"given_name": "manager",
"family_name": "name"
}