In Blazor applications, ensuring that users see only what they're authorized to is crucial. This guide will walk you through different methods of implementing authorization, both from within your markup and through procedural logic.
-
1st Approach: Using
AuthorizeView
tag.In this approach, we display specific parts of the markup conditionally based on the user's authorization status. It supports both role-based and policy-based authorization and can be as simple or as complex as you want.
<AuthorizeView Policy="NarrowedAuthenticatedUserPolicy">
<Authorized>
What narrowed authenticated user sees
</Authorized>
<NotAuthorized>
What unauthenticated user sees
</NotAuthorized>
</AuthorizeView>
<AuthorizeView Policy="SpecialAuthenticatedUserPolicy">
<Authorized>
What the special authenticated user sees
</Authorized>
</AuthorizeView>
-
2nd Approach: Using cascading
AuthenticationState
andIAuthorizationService
.When you want to check for authorization as part of procedural logic in response to user interactions. In this approach, we would use the
AuthenticationState
provided by the higher levelAuthorizeRouteView
as a cascading parameter. Then, we would inject the IAuthorizationService to authorize against a role or authorization policy.
[CascadingParameter]
private Task<AuthenticationState>? authenticationState { get; set; }
[Inject]
private IAuthorizationService AuthorizationService {get; set;}
public async Task Submit()
{
var authState = await authenticationState;
var authorizationResult = await AuthorizationService.AuthorizeAsync(authState.User, "NarrowedAuthenticatedUserPolicy");
if (authorizationResult.Succeeded)
{
// regular user logic.
}
else
{
authorizationResult = await AuthorizationService.AuthorizeAsync(authState.User, "SpecialAuthenticatedUserPolicy");
if (authorizationResult.Succeeded)
{
// Special user logic
}
else
{
// Regular authenticated user logic.
}
}
}
-
3rd Approach: using
AuthenticationStateProvider
withIAuthorizationService
.This approach is useful when you use a property in your razor markup and need to conditionally set that property based on the user's authorization status. In this case, we would inject
AuthenticationStateProvider
to get notified about authorization changes, and use the injectedAuthorizationService
to check if the user is authorized or not.
[Inject]
IAuthorizationService AuthorizationService { get; set; }
[Inject]
AuthenticationStateProvider AuthenticationStateProvider { get; set; }
public string UserContent = "What unauthenticated user sees";
protected override async Task OnInitializedAsync()
{
AuthenticationStateProvider.AuthenticationStateChanged += AuthenticationStateChangedHandler;
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState is not null)
{
await ConstructMessage(authState.User);
}
}
private async void AuthenticationStateChangedHandler(Task<AuthenticationState> authState)
{
var state = await authState;
if (state is not null && state.User is not null)
{
await ConstructMessage(state.User);
}
}
private async Task ConstructMessage(ClaimsPrincipal user)
{
var authRes = await AuthorizationService.AuthorizeAsync(user, "NarrowedAuthenticatedUserPolicy");
if (authRes.Succeeded)
{
UserContent = "What narrowed authenticated user sees";
}
else
{
authRes = await AuthorizationService.AuthorizeAsync(user, "SpecialAuthenticatedUserPolicy");
if (authRes.Succeeded)
{
UserContent = "What the special authenticated user sees";
}
}
}
Important Note: here since we're using events, it is fine to do async void. The exceptions are still caught
You may think that since CascadingAuthenticationState uses InvokeAsync
internally, then you should use it too. Unless you're well-versed with Blazor's intricacies, it's recommended to avoid using InvokeAsync
from componentBase
within your event handler
DO NOT try to find a workaround for not using async void for the event handler. If you use InvokeAsync
without awaiting it, any code that throws an exception won't be caught by blazor. It depends on your use case too, however, you can see the effect by trying to throw an exception inside the following example from Microsoft docs.
inside the second example, try to throw an error like so:
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
throw new Exception();
currentCount++;
StateHasChanged();
});
}
You will see that the component simply won't work, and no exception would be caught. This means you'd be in the dark about any errors, including their origins in the stack trace.
Further reading: https://devblogs.microsoft.com/dotnet/how-async-await-really-works/
With these approaches, you'll be better equipped to implement structured security in your Blazor applications.
Thanks for reading, Happy Halloween :)
Top comments (0)