Blazor Server Side AuthorizationHandler - PreLoad - HttpContext is Null

Tom McCartan 5 Reputation points
2024-05-08T13:56:02.2933333+00:00

I have a Multi-Tenant Blazor Server Side application, that uses Pre-Load to use HttpContext in the Razor Pages. I am setting up Authentication and Authorization using Azure Entra.

For Authorization I need to Authorize the user against the Tenant. The Tenant is identified by a Custom Header. I created a Custom AuthorizationHandler and Requirement to check the users Custom Claim (The approved Customer ID) against the Tenants Custom Header value that should match the Customer ID.

The issue is that it seems the AuthorizationHandler is called 3 times during first load. The first 2 times, the HttpContext is set and available with all the header information. But the last time (3rd time) the HttpContext is null. So when a user tries to access a page that needs to be authorized it calls the HandleRequrimentAsync, but all the Scoped Values are null? IserviceProvider and HttpContext are all null at the time of checking.

So I guess the question is:

  1. Why is it calling the Authorization Handler 3 times, and the last time all scoped variables are null? I suspect this is due to Pre-Render, as during Pre-Render I believe the values, like httpcontext are there, but in the post render after the screen displays it calls the Authorization Handler again and all variables are null.
  2. Is there a way where you can use Policy based Authorization in Blazor Server, where the handler can access Scoped Variables that are set in Program.cs? As in I am able to use Middleware to load data into scoped variables and use it during Pre-Render.

Custom Handler Code Below

public class CustomerIDAuthorizationHandler : AuthorizationHandler<CustomerIDAuthorizationPolicy>

{

private readonly IServiceProvider _serviceProvider;

private readonly IHttpContextAccessor _httpContextAccessor;

public CustomerIDAuthorizationHandler(IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor)

{

_httpContextAccessor = httpContextAccessor;

_serviceProvider = serviceProvider;

}

protected override Task HandleRequirementAsync(

AuthorizationHandlerContext context, CustomerIDAuthorizationPolicy requirement)

{

// THIS IS ALWAYS NULL

var httpContext = _httpContextAccessor.HttpContext;

var serviceProvider = _serviceProvider;

var UsersCustomerIDs = context.User.FindFirst("CUSTID");

if (UsersCustomerIDs is null)

{

return Task.CompletedTask;

}

// Split CUSTID Claim by | for users that are authroized for multiple customers

var customerIDs = UsersCustomerIDs.Value.Split('|');

if (customerIDs.Contains(requirement.CustomerID))

{

context.Succeed(requirement);

}

return Task.CompletedTask;

}

}

program.cs Code Below

builder.Services.AddScoped<IAuthorizationHandler, CustomerIDAuthorizationHandler>();

The only way I can seem to get this to work, is to manually call the Handler from code, where I want to check it in the OnInitialize. But I can't seem to use it, using the normal Attribute Based Authentication, using Policy.

protected override void OnInitialized()

{

var user = (AuthenticationStateProvider.GetAuthenticationStateAsync()).Result.User;

if (user.Identity.IsAuthenticated)

{

var requirement = new CustomerIDAuthorizationPolicy(CustomerId);

var result = AuthorizationService.AuthorizeAsync(user, null, requirement);

_IsAuthorized = result.Result.Succeeded;

}

base.OnInitialized();

}

The goal was to use this as an Authorization Policy, then use an Attribute like follows to Authorize all pages based on policy. But it seems that all scoped variables are null, in Blazor Server Side.

Desired code:

program.cs

// Add Policy to Authorize based on Client ID

builder.Services.AddAuthorization(options =>

{

options.AddPolicy("CustomerIDAuthPolicy", policy =>

policy.Requirements.Add(new CustomerIDAuthorizationPolicy()));

});

Razor _Imports Page, in a Sub Folder for Protected Pages

@attribute [Authorize(Policy = "CustomerIDAuthPolicy")]

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,419 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 57,886 Reputation points
    2024-05-09T17:20:34.1633333+00:00

    in Blazor server the lifetime of scoped services is the life of the Blazor application instance, as new instance is created for every Blazor app load. in the case of server pre-render, that is a different request and instance then the interactive load and render instance.

    your CustomerIDAuthorizationHandler is created on every request to the web service. this includes the Blazor host page that does pre-render, the request that opens the signal/r connection that loads the interactive Blazor app, and any razor page requests to handle login.

    the scoped services are created before the Blazor app is started. for a scoped service to access the HttpContext, you need to add the accessor service:

    services.AddHttpContextAccessor();
    

    this is different then Blazor HttpContext cascading state, which is only valid with the pre-render Blazor instance.

    note: the Blazor interactive instance is created by the ajax call the blazor.server.js makes, so be sure it contains the values you expect.

    note2: not sure why you don't add tenantid claim to the user and then policy requirements only need the authorization context.

    0 comments No comments