DEV Community

ramtinmovahed
ramtinmovahed

Posted on

Structured Security in Blazor: Exploring Logic-Driven Authorization

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>
Enter fullscreen mode Exit fullscreen mode
  • 2nd Approach: Using cascading AuthenticationState and IAuthorizationService.

    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 level AuthorizeRouteView 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.
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 3rd Approach: using AuthenticationStateProvider with IAuthorizationService.

    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 injected AuthorizationService 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";
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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();
    });
}
Enter fullscreen mode Exit fullscreen mode

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)