AspNetCore.Docs/aspnetcore/blazor/security/authentication-state.md

21 KiB

title author description monikerRange ms.author ms.custom ms.date uid zone_pivot_groups
ASP.NET Core Blazor authentication state guardrex Learn how to create a custom authentication state provider and receive notifications of user authentication state changes. >= aspnetcore-3.1 riande mvc 11/12/2024 blazor/security/authentication-state blazor-app-models

ASP.NET Core Blazor authentication state

[!INCLUDE]

This article explains how to create a custom authentication state provider and receive user authentication state change notifications in code.

The general approaches taken for server-side and client-side Blazor apps are similar but differ in their exact implementations, so this article pivots between server-side Blazor apps and client-side Blazor apps. Use the pivot selector at the top of the article to change the article's pivot to match the type of Blazor project that you're working with:

  • Server-side Blazor apps (:::no-loc text="Server"::: pivot): Blazor Server for .NET 7 or earlier and the server project of a Blazor Web App for .NET 8 or later.
  • Client-side Blazor apps (Blazor WebAssembly pivot): Blazor WebAssembly for all versions of .NET or the .Client project of a Blazor Web App for .NET 8 or later.

Abstract AuthenticationStateProvider class

The Blazor framework includes an abstract xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider class to provide information about the authentication state of the current user with the following members:

Implement a custom AuthenticationStateProvider

The app must reference the Microsoft.AspNetCore.Components.Authorization NuGet package, which provides authentication and authorization support for Blazor apps.

[!INCLUDE]

:::zone pivot="server"

:::moniker range=">= aspnetcore-8.0"

Configure the following authentication, authorization, and cascading authentication state services in the Program file.

When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app is preconfigured with the following service registrations, which includes exposing the authentication state as a cascading parameter. For more information, see xref:blazor/security/index#expose-the-authentication-state-as-a-cascading-parameter with additional information presented in the article's Customize unauthorized content with the Router component section.

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddAuthorization();
builder.Services.AddCascadingAuthenticationState();

:::moniker-end

:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0"

Configure authentication and authorization services in the Program file.

When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the following service registration.

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddAuthorization();

:::moniker-end

:::moniker range="< aspnetcore-6.0"

Configure authentication and authorization services in Startup.ConfigureServices of Startup.cs.

When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the following service registration.

using Microsoft.AspNetCore.Components.Authorization;

...

services.AddAuthorization();

:::moniker-end

:::zone-end

:::zone pivot="webassembly"

In Blazor WebAssembly apps (all .NET versions) or the .Client project of a Blazor Web App (.NET 8 or later), configure authentication, authorization, and cascading authentication state services in the Program file.

When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app is preconfigured with the following service registrations, which includes exposing the authentication state as a cascading parameter. For more information, see xref:blazor/security/index#expose-the-authentication-state-as-a-cascading-parameter with additional information presented in the article's Customize unauthorized content with the Router component section.

:::moniker range=">= aspnetcore-8.0"

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();

:::moniker-end

:::moniker range="< aspnetcore-8.0"

Configure authentication and authorization services in the Program file.

When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the following service registration.

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddAuthorizationCore();

:::moniker-end

:::zone-end

Subclass xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider and override xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider.GetAuthenticationStateAsync%2A to create the user's authentication state. In the following example, all users are authenticated with the username mrfibuli.

CustomAuthStateProvider.cs:

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

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

        var user = new ClaimsPrincipal(identity);

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

[!NOTE] The preceding code that creates a new xref:System.Security.Claims.ClaimsIdentity uses simplified collection initialization introduced with C# 12 (.NET 8). For more information, see Collection expressions - C# language reference.

:::zone pivot="server"

:::moniker range=">= aspnetcore-8.0"

The CustomAuthStateProvider service is registered in the Program file. Register the service scoped with xref:Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddScoped%2A:

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

:::moniker-end

:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0"

In a Blazor Server app, register the service scoped with xref:Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddScoped%2A after the call to xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A:

builder.Services.AddServerSideBlazor();

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

:::moniker-end

:::moniker range="< aspnetcore-6.0"

In a Blazor Server app, register the service scoped with xref:Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddScoped%2A after the call to xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A:

services.AddServerSideBlazor();

services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();

:::moniker-end

:::zone-end

:::zone pivot="webassembly"

The CustomAuthStateProvider service is registered in the Program file. Register the service singleton with xref:Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton%2A:

builder.Services.AddSingleton<AuthenticationStateProvider, CustomAuthStateProvider>();

:::zone-end

If it isn't present, add an @using statement to the _Imports.razor file to make the xref:Microsoft.AspNetCore.Components.Authorization?displayProperty=fullName namespace available across components:

@using Microsoft.AspNetCore.Components.Authorization;

:::moniker range=">= aspnetcore-8.0"

Confirm or change the route view component to an xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView in the xref:Microsoft.AspNetCore.Components.Routing.Router component definition. The location of the Router component differs depending on the type of app. Use search to locate the component if you're unaware of its location in the project.

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

[!NOTE] When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView component. For more information, see xref:blazor/security/index#expose-the-authentication-state-as-a-cascading-parameter with additional information presented in the article's Customize unauthorized content with the Router component section.

:::moniker-end

:::moniker range="< aspnetcore-8.0"

Where the xref:Microsoft.AspNetCore.Components.Routing.Router component is located:

The location of the Router component differs depending on the type of app. Use search to locate the component if you're unaware of its location in the project.

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

[!NOTE] When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView and xref:Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState components. For more information, see xref:blazor/security/index#expose-the-authentication-state-as-a-cascading-parameter with additional information presented in the article's Customize unauthorized content with the Router component section.

:::moniker-end

The following example xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeView component demonstrates the authenticated user's name:

<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 xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeView, see xref:blazor/security/index#authorizeview-component.

Authentication state change notifications

A custom AuthenticationStateProvider can invoke xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider.NotifyAuthenticationStateChanged%2A on the xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider base class to notify consumers of the authentication state change to rerender.

The following example is based on implementing a custom xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider by following the guidance in the Implement a custom AuthenticationStateProvider section earlier in this article. If you already followed the guidance in that section, the following CustomAuthStateProvider replaces the one shown in the section.

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

CustomAuthStateProvider.cs:

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

public class CustomAuthStateProvider : 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 userIdentifier)
    {
        var identity = new ClaimsIdentity(
        [
            new Claim(ClaimTypes.Name, userIdentifier),
        ], "Custom Authentication");

        var user = new ClaimsPrincipal(identity);

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

[!NOTE] The preceding code that creates a new xref:System.Security.Claims.ClaimsIdentity uses simplified collection initialization introduced with C# 12 (.NET 8). For more information, see Collection expressions - C# language reference.

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()
    {
        ((CustomAuthStateProvider)AuthenticationStateProvider)
            .AuthenticateUser(userIdentifier);
    }
}

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

CustomAuthenticationService.cs:

using System.Security.Claims;

public class CustomAuthenticationService
{
    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);
            }
        }
    }
}

:::zone pivot="server"

:::moniker range=">= aspnetcore-6.0"

In the Program file, register the CustomAuthenticationService in the dependency injection container:

builder.Services.AddScoped<CustomAuthenticationService>();

:::moniker-end

:::moniker range="< aspnetcore-6.0"

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

services.AddScoped<CustomAuthenticationService>();

:::moniker-end

:::zone-end

:::zone pivot="webassembly"

In the Program file, register the CustomAuthenticationService in the dependency injection container:

builder.Services.AddSingleton<CustomAuthenticationService>();

:::zone-end

The following CustomAuthStateProvider subscribes to the CustomAuthenticationService.UserChanged event. The GetAuthenticationStateAsync method returns the user's authentication state. Initially, the authentication state is based on the value of the CustomAuthenticationService.CurrentUser. When the user changes, a new authentication state is created for the new user (new AuthenticationState(newUser)) for calls to GetAuthenticationStateAsync:

using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState authenticationState;

    public CustomAuthStateProvider(CustomAuthenticationService service)
    {
        authenticationState = new AuthenticationState(service.CurrentUser);

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

    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 CustomAuthenticationService.CurrentUser:

@using System.Security.Claims
@inject CustomAuthenticationService AuthService

<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 = AuthService.CurrentUser;

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

        var newUser = new ClaimsPrincipal(identity);

        AuthService.CurrentUser = newUser;
    }
}

[!NOTE] The preceding code that creates a new xref:System.Security.Claims.ClaimsIdentity uses simplified collection initialization introduced with C# 12 (.NET 8). For more information, see Collection expressions - C# language reference.

Additional resources

:::moniker range=">= aspnetcore-8.0"

:::moniker-end

:::moniker range="< aspnetcore-8.0"

:::moniker-end