Secure ASP.NET Core Blazor Server apps

This article explains how to secure Blazor Server apps as ASP.NET Core applications.

Blazor Server apps are configured for security in the same manner as ASP.NET Core apps. For more information, see the articles under ASP.NET Core security topics. Topics under this overview apply specifically to Blazor Server.

In Blazor Server apps, the authentication context is only established when the app starts, which is when the app first connects to the WebSocket. The authentication context is maintained for the lifetime of the circuit. Blazor Server apps periodically revalidate the user's authentication state, currently every 30 minutes by default.

If the app must capture users for custom services or react to updates to the user, see ASP.NET Core Blazor Server additional security scenarios.

Blazor Server differs from a traditional server-rendered web apps that make new HTTP requests with cookies on every page navigation. Authentication is checked during navigation events. However, cookies aren't involved. Cookies are only sent when making an HTTP request to a server, which isn't what happens when the user navigates in a Blazor Server app. During navigation, the user's authentication state is checked within the Blazor circuit, which you can update at any time on the server using the RevalidatingAuthenticationStateProvider abstraction.

Note

Implementing a custom NavigationManager to achieve authentication validation during navigation isn't recommended for Blazor Server apps. If the app must execute custom authentication state logic during navigation, use a custom AuthenticationStateProvider.

Blazor Server project template

The Blazor Server project template can be configured for authentication when the project is created.

Follow the Visual Studio guidance in Tooling for ASP.NET Core Blazor to create a new Blazor Server project with an authentication mechanism.

After choosing the Blazor Server App template and configuring the project, select the app's authentication under Authentication type:

Scaffold Identity

For more information on scaffolding Identity into a Blazor Server project, see Scaffold Identity in ASP.NET Core projects.

Scaffold Identity into a Blazor Server project:

Additional claims and tokens from external providers

To store additional claims from external providers, see Persist additional claims and tokens from external providers in ASP.NET Core.

Azure App Service on Linux with Identity Server

Specify the issuer explicitly when deploying to Azure App Service on Linux with Identity Server. For more information, see Introduction to authentication for Single Page Apps on ASP.NET Core.

Implement a custom AuthenticationStateProvider

If the app requires a custom provider, implement AuthenticationStateProvider and override GetAuthenticationStateAsync.

In the following example, all users are authenticated with the username mrfibuli.

CustomAuthenticationStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var identity = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, "mrfibuli"),
        }, "Custom Authentication");

        var user = new ClaimsPrincipal(identity);

        return Task.FromResult(new AuthenticationState(user));
    }
}

The CustomAuthenticationStateProvider service is registered in Program.cs after the call to AddServerSideBlazor:

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddServerSideBlazor();

...

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

The CustomAuthenticationStateProvider service is registered in Startup.ConfigureServices of Startup.cs after the call to AddServerSideBlazor:

using Microsoft.AspNetCore.Components.Authorization;

...

services.AddServerSideBlazor();

...

services.AddScoped<AuthenticationStateProvider, 
    CustomAuthenticationStateProvider>();

Confirm or add an AuthorizeRouteView and CascadingAuthenticationState to the App component.

In App.razor:

<CascadingAuthenticationState>
    <Router ...>
        <Found ...>
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)" />
            ...
        </Found>
        <NotFound>
            ...
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Note

When you create a Blazor app from one of the Blazor project templates with authentication enabled, the App component includes the AuthorizeRouteView and CascadingAuthenticationState components shown in the preceding example. For more information, see ASP.NET Core Blazor authentication and authorization with additional information presented in the article's Customize unauthorized content with the Router component section.

An AuthorizeView demonstrates the authenticated user's name in any component:

<AuthorizeView>
    <Authorized>
        <p>Hello, @context.User.Identity.Name!</p>
    </Authorized>
    <NotAuthorized>
        <p>You're not authorized.</p>
    </NotAuthorized>
</AuthorizeView>

For guidance on the use of AuthorizeView, see ASP.NET Core Blazor authentication and authorization.

Notification about authentication state changes

A custom AuthenticationStateProvider can invoke NotifyAuthenticationStateChanged on the AuthenticationStateProvider base class to notify consumers of the authentication state change to rerender.

The following example is based on implementing a custom AuthenticationStateProvider by following the guidance in the Implement a custom AuthenticationStateProvider section.

The following CustomAuthenticationStateProvider implementation exposes a custom method, AuthenticateUser, to sign in a user and notify consumers of the authentication state change.

CustomAuthenticationStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var identity = new ClaimsIdentity();
        var user = new ClaimsPrincipal(identity);

        return Task.FromResult(new AuthenticationState(user));
    }

    public void AuthenticateUser(string emailAddress)
    {
        var identity = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, emailAddress),
        }, "Custom Authentication");

        var user = new ClaimsPrincipal(identity);

        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(user)));
    }
}

In a component:

@inject AuthenticationStateProvider AuthenticationStateProvider

<input @bind="userIdentifier" />
<button @onclick="SignIn">Sign in</button>

<AuthorizeView>
    <Authorized>
        <p>Hello, @context.User.Identity.Name!</p>
    </Authorized>
    <NotAuthorized>
        <p>You're not authorized.</p>
    </NotAuthorized>
</AuthorizeView>

@code {
    public string userIdentifier = string.Empty;

    private void SignIn()
    {
        ((CustomAuthenticationStateProvider)AuthenticationStateProvider)
            .AuthenticateUser(userIdentifier);
    }
}

The preceding approach can be enhanced to trigger notifications of authentication state changes via a custom service. The following AuthenticationService maintains the current user's claims principal in a backing field (currentUser) with an event (UserChanged) that the AuthenticationStateProvider can subscribe to, where the event invokes NotifyAuthenticationStateChanged. With the additional configuration later in this section, the AuthenticationService can be injected into a component with logic that sets the CurrentUser to trigger the UserChanged event.

using System.Security.Claims;

public class AuthenticationService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

In Program.cs, register the AuthenticationService in the dependency injection container:

builder.Services.AddSingleton<AuthenticationService>();

In Startup.ConfigureServices of Startup.cs, register the AuthenticationService in the dependency injection container:

services.AddSingleton<AuthenticationService>();

The following CustomAuthenticationStateProvider subscribes to the AuthenticationService.UserChanged event. GetAuthenticationStateAsync returns the authentication state of the service's current user (AuthenticationService.CurrentUser).

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private AuthenticationState authenticationState;

    public CustomAuthenticationStateProvider(AuthenticationService service)
    {
        authenticationState = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            NotifyAuthenticationStateChanged(
                Task.FromResult(new AuthenticationState(newUser)));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(authenticationState);
}

The following component's SignIn method creates a claims principal for the user's identifier to set on AuthenticationService.CurrentUser:

@inject AuthenticationStateProvider AuthenticationStateProvider

<input @bind="userIdentifier" />
<button @onclick="SignIn">Sign in</button>

<AuthorizeView>
    <Authorized>
        <p>Hello, @context.User.Identity.Name!</p>
    </Authorized>
    <NotAuthorized>
        <p>You're not authorized.</p>
    </NotAuthorized>
</AuthorizeView>

@code {
    public string userIdentifier = string.Empty;

    private void SignIn()
    {
        var currentUser = AuthenticationService.CurrentUser;

        var identity = new ClaimsIdentity(
            new[]
            {
                new Claim(ClaimTypes.Name, userIdentifier),
            },
            "Custom Authentication");

        var newUser = new ClaimsPrincipal(identity);

        AuthenticationService.CurrentUser = newUser;
    }
}

Inject AuthenticationStateProvider for services scoped to a component

Don't attempt to resolve AuthenticationStateProvider within a custom scope because it results in the creation of a new instance of the AuthenticationStateProvider that isn't correctly initialized.

To access the AuthenticationStateProvider within a service scoped to a component, inject the AuthenticationStateProvider with the @inject directive or the [Inject] attribute and pass it to the service as a parameter. This approach ensures that the correct, initialized instance of the AuthenticationStateProvider is used for each user app instance.

ExampleService.cs:

public class ExampleService
{
    public async Task<string> ExampleMethod(AuthenticationStateProvider authStateProvider)
    {
        var authState = await authStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            return $"{user.Identity.Name} is authenticated.";
        }
        else
        {
            return "The user is NOT authenticated.";
        }
    }
}

Register the service as scoped. In a Blazor Server app, scoped services have a lifetime equal to the duration of the client connection circuit.

In Program.cs:

builder.Services.AddScoped<ExampleService>();

In Startup.ConfigureServices of Startup.cs:

services.AddScoped<ExampleService>();

In the following InjectAuthStateProvider component:

Pages/InjectAuthStateProvider.razor:

@page "/inject-auth-state-provider"
@inject AuthenticationStateProvider AuthenticationStateProvider
@inherits OwningComponentBase

<h1>Inject <code>AuthenticationStateProvider</code> Example</h1>

<p>@message</p>

@code {
    private string? message;
    private ExampleService? ExampleService { get; set; }

    protected override async Task OnInitializedAsync()
    {
        ExampleService = ScopedServices.GetRequiredService<ExampleService>();

        message = await ExampleService.ExampleMethod(AuthenticationStateProvider);
    }
}
@page "/inject-auth-state-provider"
@inject AuthenticationStateProvider AuthenticationStateProvider
@inherits OwningComponentBase

<h1>Inject <code>AuthenticationStateProvider</code> Example</h1>

<p>@message</p>

@code {
    private string message;
    private ExampleService ExampleService { get; set; }

    protected override async Task OnInitializedAsync()
    {
        ExampleService = ScopedServices.GetRequiredService<ExampleService>();

        message = await ExampleService.ExampleMethod(AuthenticationStateProvider);
    }
}

For more information, see the guidance on OwningComponentBase in ASP.NET Core Blazor dependency injection.

Unauthorized content display during prerendering

To avoid showing unauthorized content during prerendering, implement IHostEnvironmentAuthenticationStateProvider to support prerendering, disable prerendering, maintain the current behavior, or authenticate the user on the server before the app starts.

User state management

In spite of the word "state" in the name, AuthenticationStateProvider isn't for storing general user state. AuthenticationStateProvider only indicates the user's authentication state to the app, whether they are signed into the app and who they are signed in as.

In Blazor Server apps, authentication uses the same ASP.NET Core Identity authentication as Razor Pages and MVC apps. The user state stored for ASP.NET Core Identity flows to Blazor without adding additional code to the app. Follow the guidance in the ASP.NET Core Identity articles and tutorials for the Identity features to take effect in the Blazor parts of the app.

For guidance on general state management outside of ASP.NET Core Identity, see ASP.NET Core Blazor state management.

Additional security abstractions

Two additional abstractions participate in managing authentication state:

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Additional resources