Authentication and Authorisation in Blazor

Blazor integrates tightly with ASP.NET Core's authentication and authorisation system. Whether you're using cookie authentication, OpenID Connect, or Identity, the component-level authorisation model stays the same. Here's how to set it up and use it effectively.

Setting Up Authentication

The simplest starting point is ASP.NET Core Identity, which is now fully supported in Blazor with .NET 8:

Program.cs
builder.Services.AddAuthentication(IdentityConstants.ApplicationScheme)
    .AddIdentityCookies();

builder.Services.AddAuthorizationBuilder();

builder.Services
    .AddIdentityCore<ApplicationUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddSignInManager()
    .AddDefaultTokenProviders();

builder.Services.AddCascadingAuthenticationState();

The AddCascadingAuthenticationState call is crucial — it makes the Task<AuthenticationState> available as a cascading parameter throughout the component tree.

The AuthorizeView Component

AuthorizeView conditionally renders content based on the user's authentication state:

razor
<AuthorizeView>
    <Authorized>
        <p>Welcome, @context.User.Identity?.Name!</p>
        <a href="/account/logout">Log out</a>
    </Authorized>
    <NotAuthorized>
        <a href="/account/login">Log in</a>
    </NotAuthorized>
    <Authorizing>
        <p>Checking authentication...</p>
    </Authorizing>
</AuthorizeView>

You can also check for specific roles or policies:

razor
<AuthorizeView Roles="Admin,Manager">
    <Authorized>
        <button @onclick="DeleteAll">Delete All Records</button>
    </Authorized>
</AuthorizeView>

<AuthorizeView Policy="CanEditContent">
    <Authorized>
        <EditToolbar />
    </Authorized>
</AuthorizeView>

Page-Level Authorisation

Apply the [Authorize] attribute to protect entire pages:

razor
@page "/admin/users"
@attribute [Authorize(Roles = "Admin")]

<h1>User Management</h1>
@* Only accessible to admins *@

When an unauthorised user hits this page, Blazor redirects them to the login page (or shows a "not authorised" message, depending on your configuration).

Configure the redirect in your App.razor or router:

App.razor
<CascadingAuthenticationState>
    <Router AppAssembly="typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
                <NotAuthorized>
                    @if (context.User.Identity?.IsAuthenticated != true)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p>You do not have permission to access this page.</p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
    </Router>
</CascadingAuthenticationState>

Accessing the User in Code

To access the current user's claims programmatically, inject the cascading AuthenticationState:

razor
@code {
    [CascadingParameter]
    private Task<AuthenticationState>? AuthStateTask { get; set; }

    private string? userId;
    private bool isAdmin;

    protected override async Task OnInitializedAsync()
    {
        if (AuthStateTask is not null)
        {
            var authState = await AuthStateTask;
            var user = authState.User;

            userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            isAdmin = user.IsInRole("Admin");
        }
    }
}

Alternatively, inject AuthenticationStateProvider as a service:

Example.cs
@inject AuthenticationStateProvider AuthProvider

@code {
    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthProvider.GetAuthenticationStateAsync();
        var user = authState.User;
    }
}

Custom Authorisation Policies

For fine-grained control, define custom policies:

Program.cs
builder.Services.AddAuthorizationBuilder()
    .AddPolicy("CanManageOrders", policy =>
        policy.RequireRole("Admin", "OrderManager")
              .RequireClaim("Department", "Sales", "Operations"))
    .AddPolicy("MinimumAge", policy =>
        policy.AddRequirements(new MinimumAgeRequirement(18)));

Implement a custom requirement handler:

MinimumAgeHandler.cs
public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }
    public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;
}

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst("DateOfBirth");

        if (dateOfBirthClaim is not null &&
            DateTime.TryParse(dateOfBirthClaim.Value, out var dob))
        {
            var age = DateTime.Today.Year - dob.Year;
            if (age >= requirement.MinimumAge)
            {
                context.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }
}

Register the handler:

Example.cs
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

WebAssembly Considerations

In Blazor WebAssembly, authentication state lives on the client. You need a custom AuthenticationStateProvider that reads tokens (typically JWTs) and creates the ClaimsPrincipal:

JwtAuthStateProvider.cs
public class JwtAuthStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _http;

    public JwtAuthStateProvider(HttpClient http) => _http = http;

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var token = await GetTokenFromStorage();

        if (string.IsNullOrEmpty(token))
        {
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        var claims = ParseClaimsFromJwt(token);
        var identity = new ClaimsIdentity(claims, "jwt");
        _http.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", token);

        return new AuthenticationState(new ClaimsPrincipal(identity));
    }
}

Remember that client-side authorisation is purely cosmetic — the UI enforcement. Your API must always validate tokens and enforce permissions independently. Never trust the client.

Summary

Blazor's authorisation system is consistent and composable. Use AuthorizeView for conditional UI, [Authorize] for page protection, and custom policies for complex rules. The cascading AuthenticationState gives every component easy access to the current user. Just remember that in WebAssembly mode, all client-side checks are supplementary to server-side enforcement.