Search code examples
apiblazoridentityserver4webassembly

Blazor wasm get additional information and add to user claims


I am using identityserver4 for authentication and it's laid out something like this: identity server4 -> Web Api -> Blazor WASM Client(Standalone). everything is getting authenticated and working great. I get the authenticated user claims all the way to the wasm client. I am now trying to add more claims which come directly from the database. I could have added the claims to the identityserver token but the token gets too big (> 2kb) and then identityserver stops working. apparently this is a known issue.

So iwant to build authorization and trying to keep the jwt token from identityserver small.

in the program.cs file i have a http client like so

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

builder.Services.AddOidcAuthentication(options =>// Configure your authentication provider options here. // For more information, see https://aka.ms/blazor-standalone-auth //builder.Configuration.Bind("Local", options.ProviderOptions); ... provider options

            options.ProviderOptions.ResponseType = "code";
            options.UserOptions.RoleClaim = "role";                
        }).AddAccountClaimsPrincipalFactory<CustomAccountClaimsPrincipalFactory>();

await builder.Build().RunAsync();

in the file CustomAccountClaimsPrincipalFactory i have this

  public class CustomAccountClaimsPrincipalFactory
: AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    private const string Planet = "planet";

    [Inject]
    public HttpClient Http { get; set; }

    public CustomAccountClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor) {            
    }

    public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var claims = identity.Claims.Where(a => a.Type == Planet);
            if (!claims.Any())
            {
                identity.AddClaim(new Claim(Planet, "mars"));
            }

            //get user roles
           
            //var url = $"/Identity/users/112b7de8-614f-40dc-a9e2-fa6e9d2bf85a/roles";                
            var dResources = await Http.GetFromJsonAsync<List<somemodel>>("/endpoint");
            foreach (var item in dResources)
            {
                identity.AddClaim(new Claim(item.Name, item.DisplayName));
            }


        }
        return user;
    }
}

this is not working as the httpclient is not biolt when this is called and the http client uses the same builder which is building the base http client.

How do i get this to work?


Solution

  • You can create a IProfileService and customise it however you need:

    
    var builder = services.AddIdentityServer(options =>
    ...
                    .AddProfileService<IdentityProfileService>();
    
        public class IdentityProfileService : IProfileService
        {
    
            private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
            private readonly UserManager<ApplicationUser> _userManager;
    
            public IdentityProfileService(IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory, UserManager<ApplicationUser> userManager)
            {
                _claimsFactory = claimsFactory;
                _userManager = userManager;
            }
    
            public async Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                var sub = context.Subject.GetSubjectId();
                var user = await _userManager.FindByIdAsync(sub);
                if (user == null)
                {
                    throw new ArgumentException("");
                }
    
                var principal = await _claimsFactory.CreateAsync(user);
                var claims = principal.Claims.ToList();
    
                //Add more claims like this
                //claims.Add(new System.Security.Claims.Claim("MyProfileID", user.Id));
    
                context.IssuedClaims = claims;
            }
    
            public async Task IsActiveAsync(IsActiveContext context)
            {
                var sub = context.Subject.GetSubjectId();
                var user = await _userManager.FindByIdAsync(sub);
                context.IsActive = user != null;
            }
        }