Skip to content

BlazorWebAppOidc AddOpenIdConnect with GetClaimsFromUserInfoEndpoint = true doesn't propogate role claims to client #58826

Open

Description

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"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

Needs: Attention 👋This issue needs the attention of a contributor, typically because the OP has provided an update.Needs: ReproIndicates that the team needs a repro project to continue the investigation on this issuearea-security

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions