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:
- 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.
- 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")]