Skip to content

[Blazor] Custom Authorization analog of Authorize attribute #65007

Description

@mikermnv

Problem Statement

When implementing a custom permission checking system in a Blazor Server project, I encountered difficulties creating a universal solution that requires minimal code and allows protecting resource components.

Desired Flow

  1. While permissions are being checked → show a loader.
  2. After the check:
    • If access is granted → render the protected body content.
    • If access is denied → render a fragment with a message about missing rights.

This is similar to the built-in [Authorize] attribute for components and the AuthorizeView wrapper for static UI elements.
I was able to implement an AuthorizeView-like wrapper, but creating an Authorize-like attribute proved problematic.


Issues Encountered

  1. Authorize Attribute Limitations
    • Cannot render a custom fragment when access is denied (e.g., message with missing rights).
    • Cannot easily pass parameters such as a list of required permissions and a custom context model
public class SecurityContext 
{
    public int? ProjectId { get; set; }
    public int? DepartmentId { get; set; }
}

The problem is that SecurityContext in dynamicly set from url, but attribute can contains only static info. So it is not an option.
2. Layout Approach

  • Attempted to use multiple layouts to handle authorization states, but Blazor only supports a single layout per page.
  1. Inheritance Approach
    • Implemented a base component (SecurityComponentBaseCore + SecurityComponentBase) that handles loading, access checks, and error messages.

Example: SecurityComponentBaseCore.razor

@if (Loading)
{
    <FluentStack HorizontalAlignment="HorizontalAlignment.Center" VerticalAlignment="VerticalAlignment.Center" Style="height: 100%">
        <FluentProgressRing style="width: 62px; height: 62px;" />
    </FluentStack>
}
else if (HasAccess)
{
    @Body
}
else
{
    <FluentMessageBar Intent="MessageIntent.Warning">@ErrorMessage</FluentMessageBar>
}

@code {
    public virtual bool Loading { get; set; } = true;
    public virtual bool HasAccess { get; set; } = false;
    public virtual string ErrorMessage { get; set; } = string.Empty;
    private protected virtual RenderFragment Body => builder => { };
}

Example: SecurityComponentBase.cs

public abstract class SecurityComponentBase : SecurityComponentBaseCore
{
    [Inject] protected ISecurityManager SecurityManager { get; set; } = default!;
    [Inject] protected ILogger<SecurityComponentBase> Logger { get; set; } = default!;

    protected virtual SecurityContext Context { get; } = new SecurityContext();
    protected virtual List<string> RequiredPermissions { get; } = [];

    private protected sealed override RenderFragment Body => BuildRenderTree;
    protected new virtual void BuildRenderTree(RenderTreeBuilder builder) { }

    protected override async Task OnInitializedAsync()
    {
        try
        {
            Loading = true;
            var res = await SecurityManager.HasAccess(Context, RequiredPermissions);

            HasAccess = res.IsSuccess;
            Loading = false;
            if (!HasAccess)
            {
                ErrorMessage = res.ErrorMessage ?? "Access denied";
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error checking permissions");
            HasAccess = false;
            ErrorMessage = "Error checking permissions";
        }
        finally
        {
            Loading = false;
        }
    }
}

Example: MyComponent.razor

@inherits SecurityComponentBase

<div>
    content
</div>

@code {
    protected override List<string> RequiredPermissions { get; } = ["right1", "right2"];
    protected override SecurityContext Context { get; } = new SecurityContext { ProjectId = 1 };

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        // Secure work here
        await Task.Delay(5000);
    }
}

The problem in this scenario is that calling StateHasChanged() anywhere results in:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

This prevents showing the loader only during permission checks (instead it persists until the entire OnInitializedAsync of the derived component finishes and a 5‑second delay allows this to be shown). The call to StateHasChanged() is necessary in this scenario because it should trigger a re‑render: once the permission check completes, the component should update its UI so that the loader is replaced by either the protected content or the denied fragment. Without this re‑render, the loader remains visible longer than intended.


Question

Is there a proper Blazor solution that allows building an Authorize-like mechanism with:

  • A loader displayed during the permission check
  • A custom denied fragment that shows missing rights
  • The ability to pass both a list of required permissions and a context model (SecurityContext)

Is there a recommended pattern or framework support for this scenario?
Alternatively, could the existing Authorize attribute be extended or modified to handle these requirements?

Attempting to implement something that was not originally intended feels fragile, especially since Blazor evolves quickly and such a workaround may stop functioning in future versions (I have already experienced this).

Therefore, the core question is: Is there a proper, supported way to create an equivalent of Authorize or a reworked version that will reliably function in Blazor?

It is possible that I have not fully understood all aspects of Blazor’s authorization system and may be missing something important. I would be glad to read any comments, clarifications, or suggestions.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions