Check authorization rules as part of procedural logic in ASP.NET Blazor

If the app is required to check authorization rules as part of procedural logic, use a cascaded parameter of type Task<AuthenticationState> to obtain the user’s ClaimsPrincipalTask<AuthenticationState> can be combined with other services, such as IAuthorizationService, to evaluate policies.

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private async Task DoSomething()
    {
        var user = (await authenticationStateTask).User;

        if (user.Identity.IsAuthenticated)
        {
            // Perform an action only available to authenticated (signed-in) users.
        }

        if (user.IsInRole("admin"))
        {
            // Perform an action only available to users in the 'admin' role.
        }

        if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
            .Succeeded)
        {
            // Perform an action only available to users satisfying the 
            // 'content-editor' policy.
        }
    }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#procedural-logic

Customize unauthorized content with the Router component in ASP.NET Blazor

The Router component, in conjunction with the AuthorizeRouteView component, allows the app to specify custom content if:

  • The user fails an [Authorize] condition applied to the component. The markup of the <NotAuthorized> element is displayed. The [Authorize] attribute is covered in the [Authorize] attribute section.
  • Asynchronous authorization is in progress, which usually means that the process of authenticating the user is in progress. The markup of the <Authorizing> element is displayed.
  • Content isn’t found. The markup of the <NotFound> element is displayed.
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <h1>Sorry</h1>
                    <p>You're not authorized to reach this page.</p>
                    <p>You may need to log in as a different user.</p>
                </NotAuthorized>
                <Authorizing>
                    <h1>Authorization in progress</h1>
                    <p>Only visible while authorization is in progress.</p>
                </Authorizing>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <h1>Sorry</h1>
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#customize-unauthorized-content-with-the-router-component

[Authorize] attribute in ASP.NET Blazor

The [Authorize] attribute can be used in Razor components:

@page "/"
@attribute [Authorize]

You can only see this if you're signed in.

Only use [Authorize] on @page components reached via the Blazor Router. Authorization is only performed as an aspect of routing and not for child components rendered within a page. To authorize the display of specific parts within a page, use AuthorizeView instead.

The [Authorize] attribute also supports role-based or policy-based authorization. For role-based authorization, use the Roles parameter:

@page "/"
@attribute [Authorize(Roles = "admin, superuser")]

<p>You can only see this if you're in the 'admin' or 'superuser' role.</p>

For policy-based authorization, use the Policy parameter:

@page "/"
@attribute [Authorize(Policy = "content-editor")]

<p>You can only see this if you satisfy the 'content-editor' policy.</p>

If neither Roles nor Policy is specified, [Authorize] uses the default policy, which by default is to treat:

  • Authenticated (signed-in) users as authorized.
  • Unauthenticated (signed-out) users as unauthorized.

Refererences
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#authorize-attribute

Content displayed during asynchronous authentication in ASP.NET Blazor

<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you're authenticated.</p>
    </Authorized>
    <Authorizing>
        <h1>Authentication in progress</h1>
        <p>You can only see this content while authentication is in progress.</p>
    </Authorizing>
</AuthorizeView>

This approach isn’t normally applicable to Blazor Server apps. Blazor Server apps know the authentication state as soon as the state is established. Authorizing content can be provided in a Blazor Server app’s AuthorizeView component, but the content is never displayed.

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#content-displayed-during-asynchronous-authentication

Role-based and policy-based authorization in ASP.NET Blazor

The AuthorizeView component supports role-based or policy-based authorization.

For role-based authorization, use the Roles parameter:

<AuthorizeView Roles="admin, superuser">
    <p>You can only see this if you're an admin or superuser.</p>
</AuthorizeView>

For policy-based authorization, use the Policy parameter:

<AuthorizeView Policy="content-editor">
    <p>You can only see this if you satisfy the "content-editor" policy.</p>
</AuthorizeView>

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#role-based-and-policy-based-authorization

AuthorizeView component in ASP.NET Blazor

The AuthorizeView component selectively displays UI content depending on whether the user is authorized. This approach is useful when you only need to display data for the user and don’t need to use the user’s identity in procedural logic.

The component exposes a context variable of type AuthenticationState, which you can use to access information about the signed-in user:

<AuthorizeView>
    <h1>Hello, @context.User.Identity.Name!</h1>
    <p>You can only see this content if you're authenticated.</p>
</AuthorizeView>

You can also supply different content for display if the user isn’t authorized:

<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you're authorized.</p>
        <button @onclick="SecureMethod">Authorized Only Button</button>
    </Authorized>
    <NotAuthorized>
        <h1>Authentication Failure!</h1>
        <p>You're not signed in.</p>
    </NotAuthorized>
</AuthorizeView>

@code {
    private void SecureMethod() { ... }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#authorizeview-component

Expose the authentication state as a cascading parameter in ASP.NET Blazor

@page "/"

<button @onclick="LogUsername">Log username</button>

<p>@authMessage</p>

@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private string authMessage;

    private async Task LogUsername()
    {
        var authState = await authenticationStateTask;
        var user = authState.User;

        if (user.Identity.IsAuthenticated)
        {
            authMessage = $"{user.Identity.Name} is authenticated.";
        }
        else
        {
            authMessage = "The user is NOT authenticated.";
        }
    }
}

Set up the Task<AuthenticationState> cascading parameter using the AuthorizeRouteView and CascadingAuthenticationState components in the App component (App.razor):

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

In a Blazor WebAssembly App, add services for options and authorization to Program.cs:

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

In a Blazor Server app, services for options and authorization are already present, so no further action is required.

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#expose-the-authentication-state-as-a-cascading-parameter

Custom AuthenticationStateProvider in ASP.NET Blazor

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        // user is anonymous
        // ClaimsIdentity claimsIdentity = new ClaimsIdentity();
        
        // user is authenticated
        ClaimsIdentity claimsIdentity = new ClaimsIdentity("test");
        claimsIdentity.AddClaim(new Claim("AccessUserPages","true"));

        ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
        AuthenticationState authenticationState = new AuthenticationState(claimsPrincipal);
        return await Task.FromResult(authenticationState);
    }
}

Program.cs

builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();

Index,razor

@page "/"
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider

<div>
    Authentication : @authMessage
</div>

<div>
    <h5>Claims</h5>
    @if (claims.Any())
    {
        <ul>
            @foreach (var claim in claims)
            {
                <li>@claim.Type : @claim.Value</li>
            }
        </ul>   
    }
</div>

@code
{
    private string authMessage;
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();


    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity.IsAuthenticated)
        {
            authMessage = "user is authenticated";
            claims = user.Claims;
        }
        else
        {
            authMessage = "user is not authenticated";
        }
    }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#authenticationstateprovider-service

Handling Form submission with Validation in Blazor

The EditForm component is Blazor’s approach to managing user-input in a way that makes it easy to perform validation against user input. It also provides the ability to check if all validation rules have been satisfied, and present the user with validation errors if they have not.

The EditForm provides the following callbacks for handling form submission:

  • Use OnValidSubmit to assign an event handler to run when a form with valid fields is submitted.
  • Use OnInvalidSubmit to assign an event handler to run when a form with invalid fields is submitted.
  • Use OnSubmit to assign an event handler to run regardless of the form fields’ validation status. The form is validated by calling EditContext.Validate in the event handler method. If Validate returns true, the form is valid.
@page "/"
@using System.ComponentModel.DataAnnotations

<EditForm Model="@person" OnSubmit="FormSubmit">
    <DataAnnotationsValidator/>
    <ValidationSummary/>
    <div class="mb-3">
        <label for="inputFirstName" class="form-label">First Name</label>
        <InputText @bind-Value="person.FirstName" class="form-control" id="inputFirstName"></InputText>
    </div>
    <div class="mb-3">
        <label for="inputLastName" class="form-label">Last Name</label>
        <InputText @bind-Value="person.LastName" class="form-control" id="inputLastName"></InputText>
    </div>
    <div class="mb-3">
        <label for="inputAge" class="form-label">Age</label>
        <InputNumber @bind-Value="person.Age" class="form-control" id="inputAge"></InputNumber>
    </div>

    <input type="submit" class="btn btn-primary" value="Save"/>
</EditForm>

<div>Form validation : @isFormValid</div>

@code
{

    private Person person = new();
    private bool isFormValid = false;

    public class Person
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "First Name is empty")]
        public string? FirstName { get; set; }

        [Required(ErrorMessage = "Last Name is empty")]
        public string? LastName { get; set; }

        [Required]
        [Range(0, 150, ErrorMessage = "Age is not in range")]
        public int Age { get; set; } = 36;
    }

    private void FormSubmit(EditContext editContext)
    {
        if (editContext.Validate())
        {
            isFormValid = true;
        }
    }
}

You can use ValidationMessage Component instead of ValidationSummary Component to show error message for each field.

<EditForm Model="@person" OnSubmit="FormSubmit">
    <DataAnnotationsValidator/>
    <div class="mb-3">
        <label for="inputFirstName" class="form-label">First Name</label>
        <InputText @bind-Value="person.FirstName" class="form-control" id="inputFirstName"></InputText>
        <ValidationMessage For="() => person.FirstName"></ValidationMessage>
    </div>
    <div class="mb-3">
        <label for="inputLastName" class="form-label">Last Name</label>
        <InputText @bind-Value="person.LastName" class="form-control" id="inputLastName"></InputText>
        <ValidationMessage For="() => person.LastName"></ValidationMessage>
    </div>
    <div class="mb-3">
        <label for="inputAge" class="form-label">Age</label>
        <InputNumber @bind-Value="person.Age" class="form-control" id="inputAge"></InputNumber>
        <ValidationMessage For="() => person.Age"></ValidationMessage>
    </div>

    <input type="submit" class="btn btn-primary" value="Save"/>
</EditForm>

 

References
https://blazor-university.com/forms/handling-form-submission/
https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-6.0

Navigating in Blazor using the NavLink component

Use a NavLink component in place of HTML hyperlink elements (<a>) when creating navigation links. A NavLink component behaves like an <a> element, except it toggles an active CSS class based on whether its href matches the current URL. The active class helps a user understand which page is the active page among the navigation links displayed. Optionally, assign a CSS class name to NavLink.ActiveClass to apply a custom CSS class to the rendered link when the current route matches the href.

<nav class="flex-column">
    <div class="nav-item px-3">
        <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
            <span class="oi oi-home" aria-hidden="true"></span> Home
        </NavLink>
    </div>
    <div class="nav-item px-3">
        <NavLink class="nav-link" href="counter">
            <span class="oi oi-plus" aria-hidden="true"></span> Counter
        </NavLink>
    </div>
    <div class="nav-item px-3">
        <NavLink class="nav-link" href="fetchdata">
            <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
        </NavLink>
    </div>
</nav>

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-6.0#navlink-and-navmenu-components
https://blazor-university.com/routing/navigating-our-app-via-html/