-
Notifications
You must be signed in to change notification settings - Fork 10.3k
User Claims missing from Identity Endpoints breaks Authorization #52142
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
Comments
Yes, this was by design: #51177 |
@Tratcher How do you suggest Identity API consumers to handle authorization than, if they can't access the claims? For stateless bearer token auth. Or can Blazor (WASM)'s |
Authorization must always be done on the server side (where you have access to the Claims in the User). Anything done on the client is just for convenience. You can also expose your own endpoints for the API to check for specific permissions. |
@halter73 @MackinnonBuck @mkArtakMSFT @Tratcher The action of removing the claims from the GET Or ... implementing our own custom endpoint to expose the claims... What did you win? Take the below public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var emptyAuthState = ConstructEmptyAuthState();
return Task.FromResult(emptyAuthState);
}
public void Authenticate(UserInfoResponse currentUser)
{
var identity = new ClaimsIdentity(
currentUser.Claims.Select(c => new Claim(c.Key, c.Value.ToString())), // No longer possible.
"auth");
var user = new ClaimsPrincipal(identity);
var authState = new AuthenticationState(user);
NotifyAuthenticationStateChanged(Task.FromResult(authState));
}
public void UnsetAuthenticationState()
{
var emptyAuthState = ConstructEmptyAuthState();
NotifyAuthenticationStateChanged(Task.FromResult(emptyAuthState));
}
private static AuthenticationState ConstructEmptyAuthState()
{
var identity = new ClaimsIdentity();
var user = new ClaimsPrincipal(identity);
var authState = new AuthenticationState(user);
return authState;
}
} I'm having doubts on how you test and validate these entire authentication flows / scenarios from front to back. While you're at it to make this flow viable:
I don't understand why it would be a good idea to lead the user to your API using the confirmation link in a SPA + API setup that implements these Identity Endpoints. I agree that I can build custom endpoints to do all of the above but then I might as well build the other endpoints myself... |
I also stumbled into this today. It didn't break my app, since I was just trying to integrate my Blazor WASM app with the new identity endpoints as described in this official doc. However, after completing the work, I realized the identity endpoints are not useful other than to register and authenticate a user. And, the first thing you want to do after authenticating the user is check which permissions he has to decide how much to allow him to do on both the server and client side. Without the claims, you can't tell the most basic thing: is he an admin? Can he modify things on my site? I understand that this is not OIDC, so I would never be able to set scopes, which is the next thing I would want. But I must be able to at least check basic claims like the role the user belongs to. To unblock, I implemented my own Identity endpoint group by extending the built-in ones: public static class IdentityEndpoints
{
public static RouteGroupBuilder MapIdentityEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/identity")
.WithTags("Identity");
group.MapIdentityApi<GameStoreUser>();
group.MapPost("/logout", async (ClaimsPrincipal user, SignInManager<GameStoreUser> signInManager) =>
{
await signInManager.SignOutAsync();
return TypedResults.Ok();
});
group.MapGet("/manage/infoWithClaims", async Task<Results<Ok<InfoWithClaimsResponse>, ValidationProblem, NotFound>>
(ClaimsPrincipal claimsPrincipal, UserManager<GameStoreUser> userManager) =>
{
if (await userManager.GetUserAsync(claimsPrincipal) is not { } user)
{
return TypedResults.NotFound();
}
var email = await userManager.GetEmailAsync(user) ?? throw new NotSupportedException("Users must have an email.");
var response = new InfoWithClaimsResponse(
email,
await userManager.IsEmailConfirmedAsync(user),
claimsPrincipal.Claims.ToDictionary(c => c.Type, c => c.Value));
return TypedResults.Ok(response);
});
return group;
}
} Could someone elaborate on the security concerns on returning the claims? I'm also confused because I chose ASP.NET Core Identity based on this recommendation. It says there:
I have my own Blazor WASM app that talks to my own ASP.NET Core API, and nothing else. What's the right way to make the claims a key part of this scenario? |
I'm not in a position to answer security best practices questions, I assume exposing that "SecurityStamp" wasn't ideal. If I'm not mistaking, the Azure AD approach also exposes claims. By now, I also found out that there is no "token-rotation" after refresh token use which means any non-expired refresh token could still be re-used for a week+-. However, they stated this to be for simple scenario's for devices that can't handle cookies . And that is is not meant to be(come) a token server. I had hopes for this to be(come) the alternative or the preferred way over a custom JWT implementation. @julioct Interesting approach, do you prefer checking for an empty ClaimsPrincipal over authorizing the endpoint? private static void MapIdentityEndpoints(this IEndpointRouteBuilder routeBuilder)
{
var identityGroup = routeBuilder
.MapGroup(ApiRoutes.Auth)
.WithTags(nameof(ApiRoutes.Auth));
identityGroup.MapIdentityApi<User>();
identityGroup.MapGet("/user-info", (ClaimsPrincipal user) =>
{
int.TryParse(user.FindFirstValue(CustomClaimTypes.Permissions), out int permissions);
return Results.Ok(new UserInfoResponse
{
Id = user.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty,
Email = user.FindFirstValue(ClaimTypes.Email) ?? string.Empty,
Permissions = permissions,
Roles = user.FindAll(ClaimTypes.Role).Select(claim => claim.Value),
Claims = user.Claims.ToDictionary(claim => claim.Type, claim => claim.Value)
});
}).RequireAuthorization();
} |
Ohh I just copied the implementation I found in the ASP.NET Core repo just before they removed the Claims property from the /manage/info endpoint. |
I agree to this, there is no way of getting my user claims from the api endpoints anymore. |
I've created a stopgap solution: a replacement for AuthorizeView that supports Roles in .NET 8 Blazor WebAssembly apps. All the requirements are laid out in the repo, which can be found at https://github.com/carlfranklin/BlazorAuthorizeRoleView Comments welcome. |
We don't plan to add back the user claims endpoint but we are considering providing scaffolding support to provide equivalent capabitlty #58959. |
Uh oh!
There was an error while loading. Please reload this page.
Is there an existing issue for this?
Describe the bug
I did some digging, it seems like the user Claims are no longer returned from
/manage/info
from the.MapIdentityApi<User>()
.My custom authorization was depending on getting those claims.
This change broke my app, worked in RC2 not in RTM. Or is this intentional?
IdentityApiEndpointRouteBuilderExtensions.cs
, line 455:Expected Behavior
Return the user's Claims on authenticated
GET
call to/manage/info
on the Identity Endpoints.Steps To Reproduce
Upgrade from .NET 8 RC2 to .NET 8 RTM, implement the
.MapIdentityApi<User>()
, spot the 7 differences.Exceptions (if any)
/
.NET Version
8.0.100
Anything else?
Possibly related to Blazor Identity UI issues: #52063
Of which it likely affects authorization.
The text was updated successfully, but these errors were encountered: