From e2aebc204f7d4f020bf2de802f47333347e7aab0 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:49:21 -0400 Subject: [PATCH] Authentication state updates --- ...raries-and-static-server-side-rendering.md | 2 +- .../prerendering-and-integration.md | 4 +- .../blazor/security/authentication-state.md | 482 ++++++++++++++++++ aspnetcore/blazor/security/index.md | 15 +- aspnetcore/blazor/security/server/index.md | 407 +-------------- aspnetcore/docfx.json | 2 +- aspnetcore/toc.yml | 2 + 7 files changed, 499 insertions(+), 415 deletions(-) create mode 100644 aspnetcore/blazor/security/authentication-state.md diff --git a/aspnetcore/blazor/components/class-libraries-and-static-server-side-rendering.md b/aspnetcore/blazor/components/class-libraries-and-static-server-side-rendering.md index 3833a58d25d0..11c4455557ba 100644 --- a/aspnetcore/blazor/components/class-libraries-and-static-server-side-rendering.md +++ b/aspnetcore/blazor/components/class-libraries-and-static-server-side-rendering.md @@ -134,4 +134,4 @@ public HttpContext? Context { get; set; } The value is `null` during interactive rendering and is only set during static SSR. -In many cases, there are better alternatives than using . If you need to know the current URL or to perform a redirection, the APIs on work with all render modes. If you need to know the user's authentication state, use Blazor's service over using . +In many cases, there are better alternatives than using . If you need to know the current URL or to perform a redirection, the APIs on work with all render modes. If you need to know the user's authentication state, use Blazor's service ([`AuthenticationStateProvider` documentation](xref:blazor/security/authentication-state)) over using . diff --git a/aspnetcore/blazor/components/prerendering-and-integration.md b/aspnetcore/blazor/components/prerendering-and-integration.md index 1ec3eaaa95c3..0f30f3fc9578 100644 --- a/aspnetcore/blazor/components/prerendering-and-integration.md +++ b/aspnetcore/blazor/components/prerendering-and-integration.md @@ -932,8 +932,8 @@ By initializing components with the same state used during prerendering, any exp * [Stateful reconnection after prerendering](xref:blazor/components/lifecycle#stateful-reconnection-after-prerendering): Although the content in the section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps () involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see *Persist prerendered state* section of this article. * [Prerendering with JavaScript interop](xref:blazor/components/lifecycle#prerendering-with-javascript-interop) * Authentication and authorization subjects that pertain to prerendering - * [General aspects](xref:blazor/security/index#aspnet-core-blazor-authentication-and-authorization) - * [Prerendering with authentication](xref:blazor/security/webassembly/additional-scenarios#prerendering-with-authentication) + * [General aspects](xref:blazor/security/index) + * [Prerendering with authentication in hosted Blazor WebAssembly apps](xref:blazor/security/webassembly/additional-scenarios#prerendering-with-authentication) * [Host and deploy: Blazor WebAssembly](xref:blazor/host-and-deploy/webassembly) * [Handle errors: Prerendering](xref:blazor/fundamentals/handle-errors#prerendering) * is executed *twice* when prerendering: [Handle asynchronous navigation events with `OnNavigateAsync`](xref:blazor/fundamentals/routing#handle-asynchronous-navigation-events-with-onnavigateasync) diff --git a/aspnetcore/blazor/security/authentication-state.md b/aspnetcore/blazor/security/authentication-state.md new file mode 100644 index 000000000000..65add2536150 --- /dev/null +++ b/aspnetcore/blazor/security/authentication-state.md @@ -0,0 +1,482 @@ +--- +title: ASP.NET Core Blazor authentication state +author: guardrex +description: Learn how to create a custom authentication state provider and receive notifications of user authentication state changes. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 09/19/2024 +uid: blazor/security/authentication-state +zone-pivot-groups: blazor-app-models +--- +# ASP.NET Core Blazor authentication state + +[!INCLUDE[](~/includes/not-latest-version.md)] + +This article explains how to create a custom [authentication state provider](xref:blazor/security/index#authenticationstateprovider-service) 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 class to provide information about the authentication state of the current user with the following members: + +* : Asynchronously gets the authentication state of the current user. +* : An event that provides notification when the authentication state has changed. For example, this event may be raised if a user signs in or out of the app. +* : Raises an authentication state changed event. + +## Implement a custom `AuthenticationStateProvider` + +The app must reference the [`Microsoft.AspNetCore.Components.Authorization` NuGet package](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.Authorization), which provides authentication and authorization support for Blazor apps. + +[!INCLUDE[](~/includes/package-reference.md)] + +:::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 includes these calls. For more information on exposing the authentication state as a cascading parameter, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. + +```csharp +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 this call. + +```csharp +using Microsoft.AspNetCore.Components.Authorization; + +... + +builder.Services.AddAuthorization(); +``` + +:::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 this call. + +```csharp +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 includes these calls. For more information on exposing the authentication state as a cascading parameter, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. + +:::moniker range=">= aspnetcore-8.0" + +```csharp +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 this call. + +```csharp +using Microsoft.AspNetCore.Components.Authorization; + +... + +builder.Services.AddAuthorizationCore(); +``` + +:::moniker-end + +:::zone-end + +Subclass and override to create the user's authentication state. In the following example, all users are authenticated with the username `mrfibuli`. + +`CustomAuthStateProvider.cs`: + +```csharp +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; + +public class CustomAuthStateProvider : AuthenticationStateProvider +{ + public override Task 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 uses simplified collection initialization introduced with C# 12 (.NET 8). For more information, see [Collection expressions - C# language reference](/dotnet/csharp/language-reference/operators/collection-expressions). + +:::zone pivot="server" + +:::moniker range=">= aspnetcore-8.0" + +The `CustomAuthStateProvider` service is registered in the `Program` file. Register the service *scoped*: + +```csharp +builder.Services.AddScoped(); +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" + +In a Blazor Server app, register the service ***after*** the call to : + +```csharp +builder.Services.AddServerSideBlazor(); + +builder.Services.AddScoped(); +``` + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +In a Blazor Server app, register the service ***after*** the call to : + +```csharp +services.AddServerSideBlazor(); + +services.AddScoped(); +``` + +:::moniker-end + +:::zone-end + +:::zone pivot="webassembly" + +The `CustomAuthStateProvider` service is registered in the `Program` file. Register the service *singleton*: + +```csharp +builder.Services.AddSingleton(); +``` + +:::zone-end + +If it isn't already present, add an [`@using`](xref:mvc/views/razor#using) statement to the `_Imports.razor` file to make the namespace available across components: + +```razor +@using Microsoft.AspNetCore.Components.Authorization; +``` + +:::moniker range=">= aspnetcore-8.0" + +Confirm or add an to the component. The location of the `Router` component differs depending on the type of app. Use search to locate the component if its location isn't evident from looking at the project's file list. + +```razor + + + + ... + + +``` + +> [!NOTE] +> When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the in the `Router` component. For more information, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +Confirm or add an and to the component. The location of the `Router` component differs depending on the type of app. Use search to locate the component if its location isn't evident from looking at the project's file list. + +```razor + + + + + ... + + + +``` + +> [!NOTE] +> When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the and components in the `Router` component. For more information, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. + +:::moniker-end + +An demonstrates the authenticated user's name in any component: + +```razor + + +

Hello, @context.User.Identity?.Name!

+
+ +

You're not authorized.

+
+
+``` + +For guidance on the use of , see . + +## Authentication state change notifications + +A [custom `AuthenticationStateProvider`](#implement-a-custom-authenticationstateprovider) can invoke on the base class to notify consumers of the authentication state change to rerender. + +The following example is based on implementing a custom by following the guidance in the [Implement a custom `AuthenticationStateProvider`](#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`: + +```csharp +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; + +public class CustomAuthStateProvider : AuthenticationStateProvider +{ + public override Task 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 uses simplified collection initialization introduced with C# 12 (.NET 8). For more information, see [Collection expressions - C# language reference](/dotnet/csharp/language-reference/operators/collection-expressions). + +In a component: + +* Inject . +* Add a field to hold the user's identifier. +* Add a button and a method to cast the to `CustomAuthStateProvider` and call `AuthenticateUser` with the user's identifier. + +```razor +@inject AuthenticationStateProvider AuthenticationStateProvider + + + + + + +

Hello, @context.User.Identity?.Name!

+
+ +

You're not authorized.

+
+
+ +@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 can subscribe to, where the event invokes . 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`: + +```csharp +using System.Security.Claims; + +public class CustomAuthenticationService +{ + public event Action? 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: + +```csharp +builder.Services.AddScoped(); +``` + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +In `Startup.ConfigureServices` of `Startup.cs`, register the `CustomAuthenticationService` in the dependency injection container: + +```csharp +services.AddScoped(); +``` + +:::moniker-end + +:::zone-end + +:::zone pivot="webassembly" + +In the `Program` file, register the `CustomAuthenticationService` in the dependency injection container: + +```csharp +builder.Services.AddSingleton(); +``` + +:::zone-end + +The following `CustomAuthStateProvider` subscribes to the `CustomAuthenticationService.UserChanged` event. `GetAuthenticationStateAsync` returns the user's authentication state. Initially, the authentication state is based on the value of the `CustomAuthenticationService.CurrentUser`. When there's a change in user, a new authentication state is created with the new user (`new AuthenticationState(newUser)`) for calls to `GetAuthenticationStateAsync`: + +```csharp +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 GetAuthenticationStateAsync() => + Task.FromResult(authenticationState); +} +``` + +The following component's `SignIn` method creates a claims principal for the user's identifier to set on `CustomAuthenticationService.CurrentUser`: + +```razor +@using System.Security.Claims +@inject CustomAuthenticationService AuthService + + + + + + +

Hello, @context.User.Identity?.Name!

+
+ +

You're not authorized.

+
+
+ +@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 uses simplified collection initialization introduced with C# 12 (.NET 8). For more information, see [Collection expressions - C# language reference](/dotnet/csharp/language-reference/operators/collection-expressions). + +## Additional resources + +:::moniker range=">= aspnetcore-8.0" + +* [Server-side unauthorized content display while prerendering with a custom `AuthenticationStateProvider`](xref:blazor/security/server/index#unauthorized-content-display-while-prerendering-with-a-custom-authenticationstateprovider) +* [How to access an `AuthenticationStateProvider` from a `DelegatingHandler` set up using an `IHttpClientFactory`](xref:blazor/security/server/additional-scenarios#access-authenticationstateprovider-in-outgoing-request-middleware) +* +* + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +* [Server-side unauthorized content display while prerendering with a custom `AuthenticationStateProvider`](xref:blazor/security/server/index#unauthorized-content-display-while-prerendering-with-a-custom-authenticationstateprovider) +* [How to access an `AuthenticationStateProvider` from a `DelegatingHandler` set up using an `IHttpClientFactory`](xref:blazor/security/server/additional-scenarios#access-authenticationstateprovider-in-outgoing-request-middleware) +* +* +[Prerendering with authentication in hosted Blazor WebAssembly apps](xref:blazor/security/webassembly/additional-scenarios#prerendering-with-authentication) + +:::moniker-end diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index 820021a1a179..a24a4272e480 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -85,7 +85,9 @@ Blazor uses the existing ASP.NET Core authentication mechanisms to establish the Interactively-rendered server-side Blazor operates over a SignalR connection with the client. [Authentication in SignalR-based apps](xref:signalr/authn-and-authz) is handled when the connection is established. Authentication can be based on a cookie or some other bearer token, but authentication is managed via the SignalR hub and entirely within the [circuit](xref:blazor/hosting-models#blazor-server). -The built-in service obtains authentication state data from ASP.NET Core's . This is how authentication state integrates with existing ASP.NET Core authentication mechanisms. +The built-in or custom service obtains authentication state data from ASP.NET Core's . This is how authentication state integrates with existing ASP.NET Core authentication mechanisms. + +For more information on server-side authentication, see . #### `IHttpContextAccessor`/`HttpContext` in Razor components @@ -107,9 +109,9 @@ Add the following: * The namespace to the app's `_Imports.razor` file. -To handle authentication, use of the built-in or custom service is covered in the following sections. +To handle authentication, use the built-in or custom service. -For more information, see . +For more information on client-side authentication, see . ## `AuthenticationStateProvider` service @@ -127,8 +129,9 @@ For more information, see . You don't typically use directly. Use the [`AuthorizeView` component](#authorizeview-component) or [`Task`](#expose-the-authentication-state-as-a-cascading-parameter) approaches described later in this article. The main drawback to using directly is that the component isn't notified automatically if the underlying authentication state data changes. -> [!NOTE] -> To implement a custom , see . +To implement a custom , see , which includes guidance on implementing user authentication state change notifications. + +## Obtain a user's claims principal data The service can provide the current user's data, as shown in the following example. @@ -245,7 +248,7 @@ In the preceding example: If `user.Identity.IsAuthenticated` is `true` and because the user is a , claims can be enumerated and membership in roles evaluated. -For more information on dependency injection (DI) and services, see and . For information on how to implement a custom in server-side Blazor apps, see . +For more information on dependency injection (DI) and services, see and . For information on how to implement a custom , see . ## Expose the authentication state as a cascading parameter diff --git a/aspnetcore/blazor/security/server/index.md b/aspnetcore/blazor/security/server/index.md index ee5364f4f670..856c6cc08096 100644 --- a/aspnetcore/blazor/security/server/index.md +++ b/aspnetcore/blazor/security/server/index.md @@ -23,7 +23,7 @@ If the app must capture users for custom services or react to updates to the use Blazor 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 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](#additional-security-abstractions). > [!IMPORTANT] -> Implementing a custom `NavigationManager` to achieve authentication validation during navigation isn't recommended. If the app must execute custom authentication state logic during navigation, use a [custom `AuthenticationStateProvider`](#implement-a-custom-authenticationstateprovider). +> Implementing a custom `NavigationManager` to achieve authentication validation during navigation isn't recommended. If the app must execute custom authentication state logic during navigation, use a [custom `AuthenticationStateProvider`](xref:blazor/security/authentication-state#implement-a-custom-authenticationstateprovider). > [!NOTE] > The code examples in this article adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core in .NET 6 or later. When targeting ASP.NET Core 5.0 or earlier, remove the null type designation (`?`) from the examples in this article. @@ -276,409 +276,6 @@ To store additional claims from external providers, see . -## Implement a custom `AuthenticationStateProvider` - -If the app requires a custom provider, implement and override . - -In the following example, all users are authenticated with the username `mrfibuli`. - -`CustomAuthStateProvider.cs`: - -```csharp -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Authorization; - -public class CustomAuthStateProvider : AuthenticationStateProvider -{ - public override Task GetAuthenticationStateAsync() - { - var identity = new ClaimsIdentity(new[] - { - new Claim(ClaimTypes.Name, "mrfibuli"), - }, "Custom Authentication"); - - var user = new ClaimsPrincipal(identity); - - return Task.FromResult(new AuthenticationState(user)); - } -} -``` - -:::moniker range=">= aspnetcore-8.0" - -The `CustomAuthStateProvider` service is registered in the `Program` file: - -```csharp -using Microsoft.AspNetCore.Components.Authorization; - -... - -builder.Services.AddScoped(); -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" - -The `CustomAuthStateProvider` service is registered in the `Program` file ***after*** the call to : - -```csharp -using Microsoft.AspNetCore.Components.Authorization; - -... - -builder.Services.AddServerSideBlazor(); - -... - -builder.Services.AddScoped(); -``` - -:::moniker-end - -:::moniker range="< aspnetcore-6.0" - -The `CustomAuthStateProvider` service is registered in `Startup.ConfigureServices` of `Startup.cs` ***after*** the call to : - -```csharp -using Microsoft.AspNetCore.Components.Authorization; - -... - -services.AddServerSideBlazor(); - -... - -services.AddScoped(); -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -Confirm or add an to the component. - -In the `Routes` component (`Components/Routes.razor`): - -```razor - - - - ... - - -``` - -Add cascading authentication state services to the service collection in the `Program` file: - -```csharp -builder.Services.AddCascadingAuthenticationState(); -``` - -> [!NOTE] -> When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the and call to . For more information, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -Confirm or add an and to the component: - -```razor - - - - - ... - - - -``` - -> [!NOTE] -> When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the and components shown in the preceding example. For more information, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. - -:::moniker-end - -An demonstrates the authenticated user's name in any component: - -```razor - - -

Hello, @context.User.Identity?.Name!

-
- -

You're not authorized.

-
-
-``` - -For guidance on the use of , see . - -## Notification about authentication state changes - -A [custom `AuthenticationStateProvider`](#implement-a-custom-authenticationstateprovider) can invoke on the base class to notify consumers of the authentication state change to rerender. - -The following example is based on implementing a custom by following the guidance in the [Implement a custom `AuthenticationStateProvider`](#implement-a-custom-authenticationstateprovider) 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`: - -```csharp -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Authorization; - -public class CustomAuthStateProvider : AuthenticationStateProvider -{ - public override Task 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[] - { - new Claim(ClaimTypes.Name, userIdentifier), - }, "Custom Authentication"); - - var user = new ClaimsPrincipal(identity); - - NotifyAuthenticationStateChanged( - Task.FromResult(new AuthenticationState(user))); - } -} -``` - -In a component: - -* Inject . -* Add a field to hold the user's identifier. -* Add a button and a method to cast the to `CustomAuthStateProvider` and call `AuthenticateUser` with the user's identifier. - -:::moniker range=">= aspnetcore-8.0" - -```razor -@inject AuthenticationStateProvider AuthenticationStateProvider - - - - - - -

Hello, @context.User.Identity?.Name!

-
- -

You're not authorized.

-
-
- -@code { - public string userIdentifier = string.Empty; - - private void SignIn() - { - ((CustomAuthStateProvider)AuthenticationStateProvider) - .AuthenticateUser(userIdentifier); - } -} -``` - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -```razor -@inject AuthenticationStateProvider AuthenticationStateProvider - - - - - - -

Hello, @context.User.Identity?.Name!

-
- -

You're not authorized.

-
-
- -@code { - public string userIdentifier = string.Empty; - - private void SignIn() - { - ((CustomAuthStateProvider)AuthenticationStateProvider) - .AuthenticateUser(userIdentifier); - } -} -``` - -:::moniker-end - -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 can subscribe to, where the event invokes . 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. - -```csharp -using System.Security.Claims; - -public class CustomAuthenticationService -{ - public event Action? UserChanged; - private ClaimsPrincipal? currentUser; - - public ClaimsPrincipal CurrentUser - { - get { return currentUser ?? new(); } - set - { - currentUser = value; - - if (UserChanged is not null) - { - UserChanged(currentUser); - } - } - } -} -``` - -:::moniker range=">= aspnetcore-6.0" - -In the `Program` file, register the `CustomAuthenticationService` in the dependency injection container: - -```csharp -builder.Services.AddScoped(); -``` - -:::moniker-end - -:::moniker range="< aspnetcore-6.0" - -In `Startup.ConfigureServices` of `Startup.cs`, register the `CustomAuthenticationService` in the dependency injection container: - -```csharp -services.AddScoped(); -``` - -:::moniker-end - -The following `CustomAuthStateProvider` subscribes to the `CustomAuthenticationService.UserChanged` event. `GetAuthenticationStateAsync` returns the user's authentication state. Initially, the authentication state is based on the value of the `CustomAuthenticationService.CurrentUser`. When there's a change in user, a new authentication state is created with the new user (`new AuthenticationState(newUser)`) for calls to `GetAuthenticationStateAsync`: - -```csharp -using System.Security.Claims; -using System.Threading.Tasks; -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 GetAuthenticationStateAsync() => - Task.FromResult(authenticationState); -} -``` - -The following component's `SignIn` method creates a claims principal for the user's identifier to set on `CustomAuthenticationService.CurrentUser`: - -:::moniker range=">= aspnetcore-8.0" - -```razor -@inject CustomAuthenticationService AuthService - - - - - - -

Hello, @context.User.Identity?.Name!

-
- -

You're not authorized.

-
-
- -@code { - public string userIdentifier = string.Empty; - - private void SignIn() - { - var currentUser = AuthService.CurrentUser; - - var identity = new ClaimsIdentity( - new[] - { - new Claim(ClaimTypes.Name, userIdentifier), - }, - "Custom Authentication"); - - var newUser = new ClaimsPrincipal(identity); - - AuthService.CurrentUser = newUser; - } -} -``` - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -```razor -@inject CustomAuthenticationService AuthService - - - - - - -

Hello, @context.User.Identity?.Name!

-
- -

You're not authorized.

-
-
- -@code { - public string userIdentifier = string.Empty; - - private void SignIn() - { - var currentUser = AuthService.CurrentUser; - - var identity = new ClaimsIdentity( - new[] - { - new Claim(ClaimTypes.Name, userIdentifier), - }, - "Custom Authentication"); - - var newUser = new ClaimsPrincipal(identity); - - AuthService.CurrentUser = newUser; - } -} -``` - -:::moniker-end - ## Inject `AuthenticationStateProvider` for services scoped to a component Don't attempt to resolve within a custom scope because it results in the creation of a new instance of the that isn't correctly initialized. @@ -793,7 +390,7 @@ For more information, see the guidance on for the custom to support prerendering: For an example implementation of , see the Blazor framework's implementation in [`ServerAuthenticationStateProvider.cs` (reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Endpoints/src/DependencyInjection/ServerAuthenticationStateProvider.cs). diff --git a/aspnetcore/docfx.json b/aspnetcore/docfx.json index 6dd098433ff9..104f10fa3449 100644 --- a/aspnetcore/docfx.json +++ b/aspnetcore/docfx.json @@ -78,7 +78,7 @@ "**/tutorials/**/**.md": "tutorial" }, "no-loc": { - "**/**.md": [ "Blazor", "Blazor Hybrid", "Blazor Server", "Blazor WebAssembly", "BREACH", "cookie", "Cookie", "CRIME", "EF Core", "Home" , "global.json", "Identity", "JS", "Kestrel", ".NET Aspire", ".NET MAUI", ".NET Multi-platform App UI", "Privacy" , "Razor", "REST", "SignalR", "Web View" ] + "**/**.md": [ "Blazor", "Blazor Hybrid", "Blazor Server", "Blazor WebAssembly", "Blazor Web App", "BREACH", "cookie", "Cookie", "CRIME", "EF Core", "Home" , "global.json", "Identity", "JS", "Kestrel", ".NET Aspire", ".NET MAUI", ".NET Multi-platform App UI", "Privacy" , "Razor", "REST", "SignalR", "Web View" ] }, "recommendations": { "**/tutorials/**/**.md": "false" diff --git a/aspnetcore/toc.yml b/aspnetcore/toc.yml index a0de5b58298e..d602c269a4d6 100644 --- a/aspnetcore/toc.yml +++ b/aspnetcore/toc.yml @@ -580,6 +580,8 @@ items: items: - name: Overview uid: blazor/security/index + - name: Authentication state + uid: blazor/security/authentication-state - name: Server items: - name: Overview