dotnet / AspNetCore.Docs

Documentation for ASP.NET Core
https://docs.microsoft.com/aspnet/core
Creative Commons Attribution 4.0 International
12.53k stars 25.3k forks source link

[Suggestion] Add some instructions for getting role claims. #32512

Closed travaille-dev closed 1 month ago

travaille-dev commented 4 months ago

Description

I ran into a use case where I wanted to not only validate that a user can sign in, but that they are in a particular role before viewing a page/component.

I had made a ticket here https://github.com/dotnet/aspnetcore/issues/55314 and I thought it might be useful for others to have this documented. It could be helpful for other providers as well if they have something like multiple policies you'd like to map or another claim type with multiple values.

I am using Entra as my oidc provider, so the prerequisites for this are having an entra app and app registration in order to assign users/groups/apps to role(s).

A user can be assigned to multiple roles so I needed to adjust the UserInfo Class at https://github.com/dotnet/blazor-samples/blob/main/8.0/BlazorWebAppOidc/BlazorWebAppOidc.Client/UserInfo.cs to the following

using System.Security.Claims;

namespace BlazorWebAppOidc.Client;

// Add properties to this class and update the server and client AuthenticationStateProviders
// to expose more information about the authenticated user to the client.
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";
    private const string RoleClaimType = "roles";

    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[]
                {
                    new Claim(CustomClaimType, 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.");
}

Also the Entra string claim for the role claim is actually "roles" so you need to change the string value in https://github.com/dotnet/blazor-samples/blob/main/8.0/BlazorWebAppOidc/BlazorWebAppOidc/Program.cs This portion was elusive for me and seems silly now, but I imagine different providers can have different naming. When I was using just microsoft identity nuget packages the roles actually came through as appRole 🤷‍♀️

oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

Finally, in your UserClaims page component you can add the following in order to test out your roles

@attribute [Authorize(Roles = "Role1")]

<PageTitle>User Claims</PageTitle>
<AuthorizeView Roles="Role2">
    <Authorized>
        You have Role2
    </Authorized>
    <NotAuthorized>
        You do not have Role2
    </NotAuthorized>
</AuthorizeView>

Page URL

https://learn.microsoft.com/en-us/aspnet/core/blazor/security/blazor-web-app-with-oidc?view=aspnetcore-8.0&pivots=with-bff-pattern#configuration-1

Content source URL

https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/security/blazor-web-app-with-oidc.md

Document ID

c3346d3a-346b-8db4-2650-ec044b3f0dd9

Article author

@guardrex

github-actions[bot] commented 4 months ago

🕺💃 Happy Cinco de Mayo! 🥳🎈

A green dinosaur 🦖 will be along shortly to assist. Stand-by ........

guardrex commented 4 months ago

Thanks @travaille-dev ... Yes, I believe this would be a great addition to the BWA article. It will take a few weeks (or months 🙈) due to a heavy workload at the moment, but I'll get back to it as soon as I can 🏃‍♂️.

travaille-dev commented 4 months ago

All good here! I think this actual sample isn't even slated to be 'available' for folks until November, so we can close this if it's something already on the todo list.

guardrex commented 4 months ago

Just confirming ... it will take a little while to reach this. I have a large tutorial to write on my plate 🍽️, which will take quite a bit of time in the near-term to digest. This won't get lost ... it's on the menu 📖. (I must be hungry 🍗 ... there are a lot of food references in this comment! 🤣