[Blazor] Custom Authorization analog of Authorize attribute
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
## 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
```csharp
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.
3. **Inheritance Approach**
- Implemented a base component (`SecurityComponentBaseCore` + `SecurityComponentBase`) that handles loading, access checks, and error messages.
### Example: `SecurityComponentBaseCore.razor`
```razor
@if (Loading)
{
}
else if (HasAccess)
{
@Body
}
else
{
@ErrorMessage
}
@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`
```csharp
public abstract class SecurityComponentBase : SecurityComponentBaseCore
{
[Inject] protected ISecurityManager SecurityManager { get; set; } = default!;
[Inject] protected ILogger Logger { get; set; } = default!;
protected virtual SecurityContext Context { get; } = new SecurityContext();
protected virtual List 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`
```razor
@inherits SecurityComponentBase
content
@code {
protected override List 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.
Contributor guide
Assessment
This issue has not been assessed yet.