-
Notifications
You must be signed in to change notification settings - Fork 10.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BlazorWebAppOidc AddOpenIdConnect with GetClaimsFromUserInfoEndpoint = true doesn't propogate role claims to client #58826
Comments
Does it work if you map the "role" json key to the "role" claim type using MapJsonKey? builder.Services.AddAuthentication(MS_OIDC_SCHEME)
.AddOpenIdConnect(MS_OIDC_SCHEME, oidcOptions =>
{
// ...
oidcOptions.GetClaimsFromUserInfoEndpoint = true;
oidcOptions.ClaimActions.MapJsonKey("role", "role");
// ...
The difference from the docs is that a "role" is not unique, so we want to use |
No, I have added the MapJsonKey call and the role claims show in my logging but they are still not available on the claimsprincipal client side or at the minimal api server side to able to use them for authorization. The only claims that are in the principal identity are the ones from the ID token. |
It's really surprising to me that the claims you see in a minimal API are different than what you're logging in What do you see if you add the following minimal API endpoint? app.MapGet("/claims", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })); Since the minimal API is relying purely on the cookie authentication handler and not the Blazor The If you cannot figure out what's going on based on inspecting the |
I've added the
The output of my logging in the
And adding logging to the
So, I am only missing the role claims. The I am at a loss the same as you. So I have uploaded the repo to the following location: https://github.com/BenJags/BlazorWebAppOidc Let me know if you need more details about the keycloak responses to be able to fake them. |
Thanks for the repro. I tried it out myself with Keycloak, and I discovered that the issue is that the This "refresher" is only supposed to have an effect as the access token nears expiration, so that the cookie always contains an unexpired access token, but it winds up reissuing a cookie just about every request is because Keycloak's default access token timeout is 5 minutes, and the If you don't need the access token (see here for how it can be used to make requests to another server using Otherwise, if you decide to keep the cookie/token refreshing logic, I think the first step is to reduce this 5 minute interval in Of course, even after we fix the refreshing logic to not run every request, we'd still want to get claims from the diff --git a/BlazorWebAppOidc/CookieOidcRefresher.cs b/BlazorWebAppOidc/CookieOidcRefresher.cs
index c832924..af33cd7 100644
--- a/BlazorWebAppOidc/CookieOidcRefresher.cs
+++ b/BlazorWebAppOidc/CookieOidcRefresher.cs
@@ -1,6 +1,8 @@
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
+using System.Net.Http.Headers;
using System.Security.Claims;
+using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
@@ -86,6 +88,12 @@ internal sealed class CookieOidcRefresher(IOptionsMonitor<OpenIdConnectOptions>
ValidatedIdToken = validatedIdToken,
});
+ if (oidcOptions.GetClaimsFromUserInfoEndpoint && !string.IsNullOrEmpty(oidcConfiguration.UserInfoEndpoint))
+ {
+ await AddClaimsFromUserInfoEndpointAsync(oidcConfiguration.UserInfoEndpoint, message.AccessToken, oidcScheme,
+ validatedIdToken, validationResult.ClaimsIdentity, oidcOptions, validateContext.HttpContext.RequestAborted);
+ }
+
validateContext.ShouldRenew = true;
validateContext.ReplacePrincipal(new ClaimsPrincipal(validationResult.ClaimsIdentity));
@@ -99,4 +107,43 @@ internal sealed class CookieOidcRefresher(IOptionsMonitor<OpenIdConnectOptions>
new() { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) },
]);
}
+
+ private static async Task AddClaimsFromUserInfoEndpointAsync(string userInfoEndpoint, string accessToken, string oidcScheme,
+ JwtSecurityToken validatedIdToken, ClaimsIdentity identity, OpenIdConnectOptions options, CancellationToken cancellationToken)
+ {
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint);
+ requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
+ requestMessage.Version = options.Backchannel.DefaultRequestVersion;
+ var responseMessage = await options.Backchannel.SendAsync(requestMessage, cancellationToken);
+ responseMessage.EnsureSuccessStatusCode();
+ var userInfoResponse = await responseMessage.Content.ReadAsStringAsync(cancellationToken);
+
+ string userInfoJson;
+ var contentType = responseMessage.Content.Headers.ContentType;
+ if (contentType?.MediaType?.Equals("application/json", StringComparison.OrdinalIgnoreCase) ?? false)
+ {
+ userInfoJson = userInfoResponse;
+ }
+ else if (contentType?.MediaType?.Equals("application/jwt", StringComparison.OrdinalIgnoreCase) ?? false)
+ {
+ var userInfoEndpointJwt = new JwtSecurityToken(userInfoResponse);
+ userInfoJson = userInfoEndpointJwt.Payload.SerializeToJson();
+ }
+ else
+ {
+ return;
+ }
+
+ using var user = JsonDocument.Parse(userInfoJson);
+ options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext()
+ {
+ UserInfoEndpointResponse = userInfoResponse,
+ ValidatedIdToken = validatedIdToken,
+ });
+
+ foreach (var action in options.ClaimActions)
+ {
+ action.Run(user.RootElement, identity, options.ClaimsIssuer ?? oidcScheme);
+ }
+ }
} |
Is there an existing issue for this?
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:
With the OnUserInformationReceived logging I can see the claims coming from Keycloak:
I have amended UserInfo as follows:
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:
User Info:
The text was updated successfully, but these errors were encountered: