Hi @Danny Nguyen (WICLOUD CORPORATION)
Here I am sharing my implementation configuration details, please have a look and let me know if you have any findings, Issues still exist even upgrading into the latest runtime 10.0.9 as well. but both 10.0.8 and 10.0.9 runtime, the auth flow is working fine with the AuthenticationService.Js version 10.0.7.
Note : RemoteUserAccount is null before sign-in and still null after the login callback completes
Program.cs
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Boot order: pre-Blazor JS version gate → Blazor boot → MSAL token acquisition → ClaimFactory populates roles/permissions.
// MSAL authentication
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.Cache.CacheLocation = "localStorage";
options.ProviderOptions.Authentication.ValidateAuthority = true;
options.ProviderOptions.LoginMode = "redirect";
})
.AddAccountClaimsPrincipalFactory<ClaimFactory>();
CliamFactory.cs
public class ClaimFactory(ILogger<ClaimFactory> logger,
IHttpClientFactory httpClientFactory,
IAccessTokenProviderAccessor accessor,
NotificationService notificationService,
IApplicationInsights applicationInsights,
PermissionService permissionService) : AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
private readonly ILogger _logger = logger;
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
private readonly PermissionService _permissionService = permissionService;
private readonly IApplicationInsights _applicationInsights = applicationInsights;
private readonly NotificationService _notificationService = notificationService;
private readonly SemaphoreSlim _lock = new(1, 1);
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options)
{
try
{
await _lock.WaitAsync();
var user = await base.CreateUserAsync(account, options);
if (user.Identity?.IsAuthenticated != true)
{
_logger.LogInformation("User is not authenticated. Skipping claims population.");
return user;
}
var http = _httpClientFactory.CreateClient("Test.MyApp.API");
var identity = (ClaimsIdentity)user.Identity;
var roleTask = http.GetAsync("api/users/roles");
var permissionTask = http.GetAsync("api/users/permissions");
var isSuperAdminTask = http.GetAsync("api/users/superadmin");
await Task.WhenAll(roleTask, permissionTask, isSuperAdminTask);
var roleResponse = roleTask.Result;
var userDto = roleResponse.StatusCode == HttpStatusCode.OK ? await roleTask.Result.Content.ReadFromJsonAsync<UserDto>() : new();
var isSuperAdmin = await isSuperAdminTask.Result.Content.ReadFromJsonAsync<bool?>() ?? false;
_permissionService.SetIsSuperAdmin(isSuperAdmin);
var permissions = await permissionTask.Result.Content.ReadFromJsonAsync<HashSet<Permissions>>() ?? [];
_permissionService.SetPermissions(permissions);
List<string> newRoleClaims = [];
foreach (var userRole in userDto.UserRoles.OrderBy(x => x.BusinessId))
{
switch (userRole.BusinessId)
{
case "XXX":
identity.AddClaim(new Claim("XXX", userRole.Role));
newRoleClaims.Add($"XXXX_{userRole.Role}");
break;
case "YYY":
identity.AddClaim(new Claim("YYY", userRole.Role));
newRoleClaims.Add($"YYY_{userRole.Role}");
break;
}
}
var roleClaims = identity.FindAll(identity.RoleClaimType);
if (roleClaims == null || roleClaims.Any() == false)
{
foreach (var roleClaim in newRoleClaims)
{
identity.AddClaim(new Claim(identity.RoleClaimType, roleClaim));
}
}
return user;
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
_logger.LogWarning("Access token not available. Redirecting to login.");
return new ClaimsPrincipal();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obtaining user claims for My App.");
throw;
}
finally
{
_lock.Release();
}
}
}
CsProj:
<ItemGroup>
<PackageReference Include="BlazorApplicationInsights" Version="3.3.0" />
<PackageReference Include="Blazored.FluentValidation" Version="2.2.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.8" PrivateAssets="all" />
<PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" Version="10.0.8" />
<PackageReference Include="OpenTelemetry.Api" Version="1.15.3" />
<PackageReference Include="Telerik.UI.for.Blazor" Version="12.3.0" />
</ItemGroup>