Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Components/Authorization/src/AuthenticationStateData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Security.Claims;

namespace Microsoft.AspNetCore.Components.Authorization;

/// <summary>
/// A JSON-serializable type that represents the data that is used to create an <see cref="AuthenticationState"/>.
/// </summary>
public class AuthenticationStateData
{
/// <summary>
/// The client-readable claims that describe the <see cref="AuthenticationState.User"/>.
/// </summary>
public IList<KeyValuePair<string, string>> Claims { get; set; } = [];

/// <summary>
/// Gets the value that identifies 'Name' claims. This is used when returning the property <see cref="ClaimsIdentity.Name"/>.
/// </summary>
public string NameClaimType { get; set; } = ClaimsIdentity.DefaultNameClaimType;

/// <summary>
/// Gets the value that identifies 'Role' claims. This is used when calling <see cref="ClaimsPrincipal.IsInRole"/>.
/// </summary>
public string RoleClaimType { get; set; } = ClaimsIdentity.DefaultRoleClaimType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
namespace Microsoft.AspNetCore.Components.Authorization;

/// <summary>
/// An interface implemented by <see cref="AuthenticationStateProvider"/> classes that can receive authentication
/// state information from the host environment.
/// An interface implemented by services to receive authentication state information from the host environment.
/// If this is implemented by the host's <see cref="AuthenticationStateProvider"/>, it will receive authentication state from the HttpContext.
/// Or if this implemented service that is registered directly as an <see cref="IHostEnvironmentAuthenticationStateProvider"/>,
/// it will receive the <see cref="AuthenticationState"/> returned by <see cref="AuthenticationStateProvider.GetAuthenticationStateAsync"/>
Comment on lines +8 to +10
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// If this is implemented by the host's <see cref="AuthenticationStateProvider"/>, it will receive authentication state from the HttpContext.
/// Or if this implemented service that is registered directly as an <see cref="IHostEnvironmentAuthenticationStateProvider"/>,
/// it will receive the <see cref="AuthenticationState"/> returned by <see cref="AuthenticationStateProvider.GetAuthenticationStateAsync"/>
/// If this is implemented by the host's <see cref="AuthenticationStateProvider"/>, it receives authentication state from the HttpContext.
/// If this implemented service that is registered directly as an <see cref="IHostEnvironmentAuthenticationStateProvider"/>,
/// it receives the <see cref="AuthenticationState"/> returned by <see cref="AuthenticationStateProvider.GetAuthenticationStateAsync"/>

/// </summary>
public interface IHostEnvironmentAuthenticationStateProvider
{
Expand Down
8 changes: 8 additions & 0 deletions src/Components/Authorization/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
#nullable enable
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.AuthenticationStateData() -> void
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.Claims.get -> System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string!, string!>>!
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.Claims.set -> void
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.NameClaimType.get -> string!
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.NameClaimType.set -> void
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.RoleClaimType.get -> string!
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.RoleClaimType.set -> void
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.Components.Endpoints.DependencyInjection;
using Microsoft.AspNetCore.Components.Endpoints.Forms;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Forms.Mapping;
using Microsoft.AspNetCore.Components.Infrastructure;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand Down Expand Up @@ -66,6 +68,7 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection
ServiceDescriptor.Singleton<IPostConfigureOptions<RazorComponentsServiceOptions>, DefaultRazorComponentsServiceOptionsConfiguration>());
services.TryAddScoped<EndpointRoutingStateProvider>();
services.TryAddScoped<IRoutingStateProvider>(sp => sp.GetRequiredService<EndpointRoutingStateProvider>());
services.TryAddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
services.AddSupplyValueFromQueryProvider();
services.TryAddCascadingValue(sp => sp.GetRequiredService<EndpointHtmlRenderer>().HttpContext);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Server;
/// </summary>
public class ServerAuthenticationStateProvider : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider
{
private Task<AuthenticationState> _authenticationStateTask;
private Task<AuthenticationState>? _authenticationStateTask;

/// <inheritdoc />
public override Task<AuthenticationState> GetAuthenticationStateAsync()
Expand Down
4 changes: 4 additions & 0 deletions src/Components/Endpoints/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#nullable enable
Microsoft.AspNetCore.Components.Routing.RazorComponentsEndpointHttpContextExtensions
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.ServerAuthenticationStateProvider() -> void
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! authenticationStateTask) -> void
override Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.GetAuthenticationStateAsync() -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>!
Comment on lines +3 to +6
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this public because of layering?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or because we want it to be extendable

Copy link
Copy Markdown
Member Author

@halter73 halter73 May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mention this in the PR description:

In order to increase the consistency of using AddAuthenticationStateSerialization() with and without server interactivity, ServerAuthenticationStateProvider was type forwarded to Microsoft.AspNetCore.Components.Endpoints and registered by default by AddRazorComponents. This is also helpful for auth in completely non-interactive Blazor web projects.

This change isn't strictly necessary for this PR, but I do think it helps with the general usability AddRazorComponents without AddInteractiveServerComponents. I could have used an internal AuthenticationStateProvider that's functionally equivalent to the ServerAuthenticationStateProvider without the type forwarding to achieve the same thing, but I figured we might as well reduce duplication.

static Microsoft.AspNetCore.Components.Endpoints.Infrastructure.ComponentEndpointConventionBuilderHelper.GetEndpointRouteBuilder(Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder) -> Microsoft.AspNetCore.Routing.IEndpointRouteBuilder!
static Microsoft.AspNetCore.Components.Routing.RazorComponentsEndpointHttpContextExtensions.AcceptsInteractiveRouting(this Microsoft.AspNetCore.Http.HttpContext! context) -> bool
16 changes: 14 additions & 2 deletions src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,22 @@ internal static async Task InitializeStandardComponentServicesAsync(
var navigationManager = (IHostEnvironmentNavigationManager)httpContext.RequestServices.GetRequiredService<NavigationManager>();
navigationManager?.Initialize(GetContextBaseUri(httpContext.Request), GetFullUri(httpContext.Request));

if (httpContext.RequestServices.GetService<AuthenticationStateProvider>() is IHostEnvironmentAuthenticationStateProvider authenticationStateProvider)
var authenticationStateProvider = httpContext.RequestServices.GetService<AuthenticationStateProvider>();
if (authenticationStateProvider is IHostEnvironmentAuthenticationStateProvider hostEnvironmentAuthenticationStateProvider)
{
var authenticationState = new AuthenticationState(httpContext.User);
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
hostEnvironmentAuthenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
}

if (authenticationStateProvider != null)
{
var authStateListeners = httpContext.RequestServices.GetServices<IHostEnvironmentAuthenticationStateProvider>();
Task<AuthenticationState>? authStateTask = null;
foreach (var authStateListener in authStateListeners)
{
authStateTask ??= authenticationStateProvider.GetAuthenticationStateAsync();
authStateListener.SetAuthenticationState(authStateTask);
}
}

if (handler != null && form != null)
Expand Down
6 changes: 6 additions & 0 deletions src/Components/Server/src/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider))]
8 changes: 8 additions & 0 deletions src/Components/Server/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
#nullable enable
*REMOVED*Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider
*REMOVED*Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.ServerAuthenticationStateProvider() -> void
*REMOVED*Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! authenticationStateTask) -> void
*REMOVED*override Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.GetAuthenticationStateAsync() -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>!
Comment on lines +2 to +5
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this breaking? (For libraries for example).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The [assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider))] should make it non-breaking.

Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.ServerAuthenticationStateProvider() -> void (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! authenticationStateTask) -> void (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ConfigureWebSocketAcceptContext.get -> System.Func<Microsoft.AspNetCore.Http.HttpContext!, Microsoft.AspNetCore.Http.WebSocketAcceptContext!, System.Threading.Tasks.Task!>?
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ConfigureWebSocketAcceptContext.set -> void
Expand All @@ -7,4 +14,5 @@ Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ContentSe
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.DisableWebSocketCompression.get -> bool
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.DisableWebSocketCompression.set -> void
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ServerComponentsEndpointOptions() -> void
override Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.GetAuthenticationStateAsync() -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
static Microsoft.AspNetCore.Builder.ServerRazorComponentsEndpointConventionBuilderExtensions.AddInteractiveServerRenderMode(this Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder, System.Action<Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions!>! configure) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder!
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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

namespace Microsoft.AspNetCore.Components.WebAssembly.Server;

/// <summary>
/// Provides options for configuring the JSON serialization of the <see cref="AuthenticationState"/> provided by the server's <see cref="AuthenticationStateProvider"/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to reject this but the sentence is too long and difficult to machine translate.

Suggested change
/// Provides options for configuring the JSON serialization of the <see cref="AuthenticationState"/> provided by the server's <see cref="AuthenticationStateProvider"/>
/// Provides options for configuring the JSON serialization of the <see cref="AuthenticationState"/>. Provided by the server's <see cref="AuthenticationStateProvider"/>

/// to the WebAssembly client using <see cref="PersistentComponentState"/>.
/// </summary>
public class AuthenticationStateSerializationOptions
{
/// <summary>
/// Default constructor for <see cref="AuthenticationStateSerializationOptions"/>.
/// </summary>
public AuthenticationStateSerializationOptions()
{
SerializationCallback = SerializeAuthenticationStateAsync;
}

/// <summary>
/// If <see langword="true"/>, the default <see cref="SerializationCallback"/> will serialize all claims; otherwise, it will only serialize
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// If <see langword="true"/>, the default <see cref="SerializationCallback"/> will serialize all claims; otherwise, it will only serialize
/// If <see langword="true"/>, the default <see cref="SerializationCallback"/> serializes all claims; otherwise, it only serializes

/// the <see cref="ClaimsIdentity.NameClaimType"/> and <see cref="ClaimsIdentity.RoleClaimType"/> claims.
/// </summary>
public bool SerializeAllClaims { get; set; }

/// <summary>
/// Default implementation for converting the server's <see cref="AuthenticationState"/> to an <see cref="AuthenticationStateData"/> object
/// for JSON serialization to the client using <see cref="PersistentComponentState"/>."/>
Comment on lines +31 to +32
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Default implementation for converting the server's <see cref="AuthenticationState"/> to an <see cref="AuthenticationStateData"/> object
/// for JSON serialization to the client using <see cref="PersistentComponentState"/>."/>
/// Default implementation for converting the server's <see cref="AuthenticationState"/> to an <see cref="AuthenticationStateData"/> object.
/// This conversion is intended for JSON serialization to the client using <see cref="PersistentComponentState"/>."/>

/// </summary>
public Func<AuthenticationState, ValueTask<AuthenticationStateData?>> SerializationCallback { get; set; }

private ValueTask<AuthenticationStateData?> SerializeAuthenticationStateAsync(AuthenticationState authenticationState)
{
AuthenticationStateData? data = null;

if (authenticationState.User.Identity?.IsAuthenticated ?? false)
{
data = new AuthenticationStateData();

if (authenticationState.User.Identities.FirstOrDefault() is { } identity)
{
data.NameClaimType = identity.NameClaimType;
data.RoleClaimType = identity.RoleClaimType;
}

if (SerializeAllClaims)
{
foreach (var claim in authenticationState.User.Claims)
{
data.Claims.Add(new(claim.Type, claim.Value));
}
}
else
{
if (authenticationState.User.FindFirst(data.NameClaimType) is { } nameClaim)
{
data.Claims.Add(new(nameClaim.Type, nameClaim.Value));
}

foreach (var roleClaim in authenticationState.User.FindAll(data.RoleClaimType))
{
data.Claims.Add(new(roleClaim.Type, roleClaim.Value));
}
}
}

return ValueTask.FromResult(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Components.WebAssembly.Server;

internal sealed class AuthenticationStateSerializer : IHostEnvironmentAuthenticationStateProvider, IDisposable
{
// Do not change. This must match all versions of the server-side DeserializedAuthenticationStateProvider.PersistenceKey.
internal const string PersistenceKey = $"__internal__{nameof(AuthenticationState)}";

private readonly PersistentComponentState _state;
private readonly Func<AuthenticationState, ValueTask<AuthenticationStateData?>> _serializeCallback;
private readonly PersistingComponentStateSubscription _subscription;

private Task<AuthenticationState>? _authenticationStateTask;

public AuthenticationStateSerializer(PersistentComponentState persistentComponentState, IOptions<AuthenticationStateSerializationOptions> options)
{
_state = persistentComponentState;
_serializeCallback = options.Value.SerializationCallback;
_subscription = persistentComponentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);
}

private async Task OnPersistingAsync()
{
if (_authenticationStateTask is null)
{
throw new InvalidOperationException($"{nameof(SetAuthenticationState)} must be called before the {nameof(PersistentComponentState)}.{nameof(PersistentComponentState.RegisterOnPersisting)} callback.");
}

var authenticationStateData = await _serializeCallback(await _authenticationStateTask);
if (authenticationStateData is not null)
{
_state.PersistAsJson(PersistenceKey, authenticationStateData);
}
}

/// <inheritdoc />
public void SetAuthenticationState(Task<AuthenticationState> authenticationStateTask)
{
_authenticationStateTask = authenticationStateTask ?? throw new ArgumentNullException(nameof(authenticationStateTask));
}

public void Dispose()
{
_subscription.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#nullable enable
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.AuthenticationStateSerializationOptions() -> void
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializationCallback.get -> System.Func<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!, System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData?>>!
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializationCallback.set -> void
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializeAllClaims.get -> bool
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializeAllClaims.set -> void
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.ServeMultithreadingHeaders.get -> bool
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.ServeMultithreadingHeaders.set -> void
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.StaticAssetsManifestPath.get -> string?
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.StaticAssetsManifestPath.set -> void
static Microsoft.Extensions.DependencyInjection.WebAssemblyRazorComponentsBuilderExtensions.AddAuthenticationStateSerialization(this Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder! builder, System.Action<Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions!>? configure = null) -> Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder!
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Endpoints.Infrastructure;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand All @@ -30,6 +32,25 @@ public static IRazorComponentsBuilder AddInteractiveWebAssemblyComponents(this I
return builder;
}

/// <summary>
/// Serializes the <see cref="AuthenticationState"/> returned by the server-side <see cref="AuthenticationStateProvider"/> using <see cref="PersistentComponentState"/>
/// for use by interactive WebAssembly components via a deserializing client-side <see cref="AuthenticationStateProvider"/> which can be added by calling
/// AddAuthenticationStateDeserialization from the Microsoft.AspNetCore.Components.WebAssembly.Authentication package in the client project.
Comment on lines +36 to +38
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Serializes the <see cref="AuthenticationState"/> returned by the server-side <see cref="AuthenticationStateProvider"/> using <see cref="PersistentComponentState"/>
/// for use by interactive WebAssembly components via a deserializing client-side <see cref="AuthenticationStateProvider"/> which can be added by calling
/// AddAuthenticationStateDeserialization from the Microsoft.AspNetCore.Components.WebAssembly.Authentication package in the client project.
/// Serializes the <see cref="AuthenticationState"/> returned by the server-side <see cref="AuthenticationStateProvider"/> using <see cref="PersistentComponentState"/>.
/// Intended for use by interactive WebAssembly components via a deserializing client-side <see cref="AuthenticationStateProvider"/>. The WebAssembly can be added by calling
/// AddAuthenticationStateDeserialization from the Microsoft.AspNetCore.Components.WebAssembly.Authentication package in the client project.

/// </summary>
/// <param name="builder">The <see cref="IRazorComponentsBuilder"/>.</param>
/// <param name="configure">A callback to customize the serialization of the <see cref="AuthenticationState"/>.</param>
/// <returns>An <see cref="IRazorComponentsBuilder"/> that can be used to further customize the configuration.</returns>
public static IRazorComponentsBuilder AddAuthenticationStateSerialization(this IRazorComponentsBuilder builder, Action<AuthenticationStateSerializationOptions>? configure = null)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped<IHostEnvironmentAuthenticationStateProvider, AuthenticationStateSerializer>());
if (configure is not null)
{
builder.Services.Configure(configure);
}

return builder;
}

private class WebAssemblyEndpointProvider(IServiceProvider services) : RenderModeEndpointProvider
{
public override IEnumerable<RouteEndpointBuilder> GetEndpointBuilders(IComponentRenderMode renderMode, IApplicationBuilder applicationBuilder)
Expand Down
Loading