Blazor Server .NET 8 authorization Dependency Injection User Settings

Dennis Vossen 1 Reputation point
2025-04-03T21:03:38.8433333+00:00

I have a IUserSettings interface to use between Blazor and a Desktop. I have a task in the interface called Task GetUserSettingsAsync(AuthenticationStateProvider authenticationStateProvider). Once the website comes back from Microsoft Entra, I want to populate user settings. I have a UserSettingService class inheriting from IUserSettings to execute GetUserSettingsAsync. I want to execute a query in GetUserSettingsAsync to get the users ID from the database. Then I can inject IUserSettings into other classes and have their user ID. What I tried isn't working. IUserSettings doesn't have the user ID.

When I debug to GetUnitsOnHandQuery.cs,_userSettings.FacilityIds is null.

Sample Code:

public class UserSettingService : IUserSettings
{
    private readonly IMediator? _mediator;

    public UserSettingService(IMediator mediator)
    {
        _mediator = mediator;
    }

    public int DefaultFacilityId { get; set; }
    public int[] FacilityIds { get; set; }
    public string FullName { get; set; }
    public bool IsAdministrator { get; set; }
    public string[] LockCodes { get; set; }
    public bool RequiresPasswordChange { get; set; }
    public int UserId { get; set; }
    public string UserName { get; set; }

    public AccessLevel GetAccessLevel(string lockCodePrefix)
    {
        throw new NotImplementedException();
    }
    public bool HasPermission(string lockCode)
    {
        throw new NotImplementedException();
    }

    public async Task GetUserSettingsAsync(AuthenticationStateProvider authenticationStateProvider)
    {
        CancellationTokenSource cancellationTokenSource = new();

        var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState?.User;

        if (user?.Identity?.IsAuthenticated == true)
        {
            var userId = await _mediator.Send(new GetUserIdQuery(user.Identity.Name), cancellationTokenSource.Token);

            await SetUserSettingsAsync(userId);
            return;
        }

        throw new UnauthorizedAccessException("User is not authenticated");
    }

    private async Task SetUserSettingsAsync(string userId)
    {
        var claims = await _mediator.Send(new GetUserClaimsQuery(userId)).ConfigureAwait(false);

        var identity = new ClaimsIdentity("Custom");
        identity.AddClaims(claims);

        var userIdClaim = (identity.FindFirst("UserId")?.Value) ?? throw new InvalidOperationException("UserId claim is missing");

        UserId = int.Parse(userIdClaim);
        UserName = userId;
        DefaultFacilityId = int.Parse(identity.FindFirst("DefaultFacility")!.Value);
        FacilityIds = identity.FindAll("Facility")
            .Select(x => x.Value)
            .Select(x => int.TryParse(x, out var value) ? value : 0)
            .ToArray();
        FullName = identity.FindFirst(ClaimTypes.GivenName)?.Value ?? string.Empty;
        IsAdministrator = identity.FindFirst("IsAdministrator")?.Value == bool.TrueString;
        LockCodes = identity.FindAll(ClaimTypes.Sid)
            .Select(x => x.Value)
            .ToArray();
        RequiresPasswordChange = identity.FindFirst("RequiresPasswordChange")?.Value == bool.TrueString;
    }
I have a IUserSettings interface to use between Blazor and a Desktop. I have a task in the interface called Task GetUserSettingsAsync(AuthenticationStateProvider authenticationStateProvider). Once the website comes back from Microsoft Entra, I want to populate user settings. I have a UserSettingService class inheriting from IUserSettings to execute GetUserSettingsAsync. I want to execute a query in GetUserSettingsAsync to get the users ID from the database. Then I can inject IUserSettings into other classes and have their user ID. What I tried isn't working. IUserSettings doesn't have the user ID.

ServiceConfiguration.cs

.AddScoped<IUserSettings, UserSettingService>()

Onhandinventory.cs

await UserSettings.GetUserSettingsAsync(AuthenticationStateProvider);

Inventories = await Mediator.Send(new GetUnitsOnHandQuery(_selectedProductId, _selectedLocationId), _cancellationTokenSource.Token);

GetUnitsOnHandQuery.cs

internal class Handler : IRequestHandler<GetFacilitiesForCurrentUserQuery, `List<FacilityDto>>
{
    private readonly IMediator _mediator;
    private readonly IUserSettings _userSettings;

    public Handler(IMediator mediator, IUserSettings userSettings)
    {
         _mediator = mediator;
         _userSettings = userSettings;
     }

     public async Task<List<FacilityDto>> Handle(GetFacilitiesForCurrentUserQuery request, CancellationToken cancellationToken = default) =>
         await _mediator.Send(new GetFacilitiesQuery(_userSettings.FacilityIds), cancellationToken);
 }
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,672 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Pradeep M 7,620 Reputation points Microsoft External Staff
    2025-04-07T11:16:28.2566667+00:00

    Hi Dennis Vossen,

    Thank you for reaching out to Microsoft Q & A forum. 

    The issue you're experiencing likely stems from how IUserSettings is being populated. Since it's registered as a scoped service, calling GetUserSettingsAsync manually in a component doesn't ensure the data is available when the same service is injected elsewhere such as in your query handler. 

    To ensure user settings are initialized consistently, move the logic for populating IUserSettings into a custom AuthenticationStateProvider. For example, by extending RevalidatingServerAuthenticationStateProvider, you can trigger the GetUserSettingsAsync call automatically after the user is authenticated: 

    protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState state, CancellationToken ct)
    {
        if (state.User.Identity?.IsAuthenticated == true)
        {
            using var scope = _provider.CreateScope();
            var userSettings = scope.ServiceProvider.GetRequiredService<IUserSettings>();
            await userSettings.GetUserSettingsAsync(this);
        }
        return true;
    }
    
    

    This ensures IUserSettings is fully populated early in the pipeline and available wherever it's injected across your application.    

    If you have found the answer provided to be helpful, please click on the "Accept answer/Upvote" button so that it is useful for other members in the Microsoft Q&A community.


  2. Bruce (SqlWork.com) 74,451 Reputation points
    2025-04-12T20:51:44.9266667+00:00

    in Blazor authentication information is stored in Blazor AuthenticationStateProvider (for compatibility with WASM). The default AuthenticationStateProvider() is build on app load, and just looks at the principal defined in the HttpContext. You make a custom AuthenticationStateProvider if you want to change the behavior. Typically the custom provider just creates the user and claims.

    the RevalidatingServerAuthenticationStateProvider is part of individual identity for for Blazor. it uses the IdentityUser api to access user info. it named revalidating, because it periodically checks the database to see if user claims are up to date. You can create a Blazor server with individual accounts, then scaffold identity to get a default source.

    when you create a custom provider, you can add additional methods/interfaces you code can call by casting the injected provider. your provider can expose

    you issue seems to be the design of the UserSettingService. it should get the AuthenticationStateProvider in its constructor, or all calls to it should pass the injected AuthenticationStateProvider.

    note: when using Entra for authentication, authentication is done outside the Blazor app via razor pages. as app load the HttpContext is used to get the user principal that the Entra Identity created. Blazor is a single request, so the context is the request from the Blazor client js code to open the signal/r socket. This request includes the cookie created by the razor page authorization.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.