blazorblazor-server-side

How to persist security in Blazor while prerendering


I'm new to Blazor and having an issue with a component.

In OnInitializedAsync, we're making a call to get the data for the component. We have prerendering enabled.

By default, so OnInitializedAsync is getting called twice. As I understand it, this is to be expected. However when we make the http calls we have a method in place to add the authorization details to the request. The first time OnInitializedAsync is called, it works fine but on the second call the contextAccessor is null and as such we can't get the roles information we need. Not sure how to proceed.

public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
    if (request == null)
    {
       throw new ArgumentNullException(nameof(request), "Message cannot be null.");
    }
    // Prepare the request message copy to be modified
    MessageBuffer buffer = request.CreateBufferedCopy(int.MaxValue);
    request = buffer.CreateMessage();
    string[] userRoles;
    System.Security.Claims.ClaimsPrincipal user = Utilities.HttpContext.Current?.User;
    if (user == null)
    {

        IHttpContextAccessor contextAccessor = ServiceProviderHolder.ServiceProvider.GetService<IHttpContextAccessor>();

        user = contextAccessor?.HttpContext?.User;
        if (user != null)
        {
          AddFromPrincipal(request, user);
        }

    }else{
        AddFromPrincipal(request, user);
    }
    return null;
}


private static void AddFromPrincipal(Message request, ClaimsPrincipal user)
{
    string roles = user.GetRoles();
    string loginId = user.GetLoginId();
    string firstName = user.GetFirstName();
    string lastName = user.GetLastName();
    string email = user.GetEmail();
    string lsUniqueId = user.GetUniqueId();
    string primaryUniqueId = user.GetPrimaryUniqueId();

    IdentityHeader idHeader = new(lsUniqueId, roles, string.Empty, loginId, firstName, lastName, email, primaryUniqueId, $"{firstName} {lastName}");
    request.Headers.Add(MessageHeader.CreateHeader(CustomHeaderNames.CustomHeaderName, CustomHeaderNames.CustomHeaderNamespace, idHeader));
}

Solution

  • We have PersistentComponentState to persist prerendered state, you just need to use PersistAsJson method to serializes instance as JSON and persists it under the given key.

    Here's the code sample shared in the document.

    @implements IDisposable
    @inject PersistentComponentState ApplicationState
    
    ...
    
    @code {
        private {TYPE} data;
        private PersistingComponentStateSubscription persistingSubscription;
    
        protected override async Task OnInitializedAsync()
        {
            persistingSubscription = 
                ApplicationState.RegisterOnPersisting(PersistData);
    
            if (!ApplicationState.TryTakeFromJson<{TYPE}>(
                "{TOKEN}", out var restored))
            {
                data = await ...;
            }
            else
            {
                data = restored!;
            }
        }
    
        private Task PersistData()
        {
            ApplicationState.PersistAsJson("{TOKEN}", data);
    
            return Task.CompletedTask;
        }
    
        void IDisposable.Dispose()
        {
            persistingSubscription.Dispose();
        }
    }
    

    In the meantime, IHttpContextAccessor is not recommended as it is not something that works with Signalr apps or Blazor apps for that matter. See this case. And as a replacement, AuthenticationStateProvider shall be a good option to get user information.