Skip to content

Commit

Permalink
Build in AuthenticationStateProviders from project templates (#55821)
Browse files Browse the repository at this point in the history
  • Loading branch information
halter73 authored May 22, 2024
1 parent 86ee868 commit 5f792ae
Show file tree
Hide file tree
Showing 43 changed files with 661 additions and 419 deletions.
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"/>
/// </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!>!
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!>!
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"/>
/// 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
/// 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"/>."/>
/// </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();
}
}
7 changes: 7 additions & 0 deletions src/Components/WebAssembly/Server/src/PublicAPI.Unshipped.txt
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.
/// </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

0 comments on commit 5f792ae

Please sign in to comment.