Search code examples
c#authorizationazure-ad-b2cblazor-client-sideclaims

Blazor WASM + AAD B2C + Custom Authorisation


I have a Blazor client (WASM) app that integrates with AAD B2C for authentication.

After authentication, I want to call my own API for further authorisation information. The reason I want to do this rather than getting B2C to call my API is I will have series of different apps using the same B2C, with different claims, roles and other information etc.

I've tried every tutorial I can find, but nothing seems to wire up.

My Program.cs has this:

builder.Services.AddMsalAuthentication(options =>
{
    var settings = config.AzureAdB2C;

    var authentication = options.ProviderOptions.Authentication;
    authentication.Authority = $"{settings.Instance}{settings.Domain}/{settings.SignInPolicy}";
    authentication.ClientId = settings.ClientApplicationId;
    authentication.ValidateAuthority = false;
    options.ProviderOptions.DefaultAccessTokenScopes.Add($"{settings.ServerAppId}/{settings.DefaultScope}");
    //options.ProviderOptions.Cache.CacheLocation = "localStorage";
});

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

And for example, I've tried this:

builder.Services.AddScoped<IClaimsTransformation, UserInfoClaims>();

public class UserInfoClaims : IClaimsTransformation
{
    private static IEnumerable<SimpleClaim> roles;
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        ...

But it doesn't get hit.

Is it possible to rewrite claims in WASM after B2C authentication?

And if not, is there an event I can wire up to after successful authentication to just manage my own role-like alternative?


Solution

  • This can be done by implementing your own AccountClaimsPrincipalFactory

        public class ExampleClaimsPrincipalFactory<TAccount> : AccountClaimsPrincipalFactory<TAccount> 
        where TAccount : RemoteUserAccount
    {
        public ExampleClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
        { 
          //Any dependency injection or construction of objects 
          //inside this constructor usually leads to wasm memory exceptions
        }
    
        public async override ValueTask<ClaimsPrincipal> CreateUserAsync(TAccount account, RemoteAuthenticationUserOptions options)
        {
            var user = await base.CreateUserAsync(account, options);
    
            if (account != null)
            {     
                //Add logic here to get custom user information
                //Add Claims to the user identity like so
                var identity = user.Identity as ClaimsIdentity;
                identity.AddClaim(new Claim("type", "value"));
            }
    
            return user;
        }
    }
    

    Then on start up when adding authentication you do the following

    builder.Services.AddMsalAuthentication()
        .AddAccountClaimsPrincipalFactory<ExampleClaimsPrincipalFactory<RemoteUserAccount>>();