Skip to content

Commit 5f792ae

Browse files
authored
Build in AuthenticationStateProviders from project templates (#55821)
1 parent 86ee868 commit 5f792ae

File tree

43 files changed

+661
-419
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+661
-419
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Security.Claims;
5+
6+
namespace Microsoft.AspNetCore.Components.Authorization;
7+
8+
/// <summary>
9+
/// A JSON-serializable type that represents the data that is used to create an <see cref="AuthenticationState"/>.
10+
/// </summary>
11+
public class AuthenticationStateData
12+
{
13+
/// <summary>
14+
/// The client-readable claims that describe the <see cref="AuthenticationState.User"/>.
15+
/// </summary>
16+
public IList<KeyValuePair<string, string>> Claims { get; set; } = [];
17+
18+
/// <summary>
19+
/// Gets the value that identifies 'Name' claims. This is used when returning the property <see cref="ClaimsIdentity.Name"/>.
20+
/// </summary>
21+
public string NameClaimType { get; set; } = ClaimsIdentity.DefaultNameClaimType;
22+
23+
/// <summary>
24+
/// Gets the value that identifies 'Role' claims. This is used when calling <see cref="ClaimsPrincipal.IsInRole"/>.
25+
/// </summary>
26+
public string RoleClaimType { get; set; } = ClaimsIdentity.DefaultRoleClaimType;
27+
}

src/Components/Authorization/src/IHostEnvironmentAuthenticationStateProvider.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
namespace Microsoft.AspNetCore.Components.Authorization;
55

66
/// <summary>
7-
/// An interface implemented by <see cref="AuthenticationStateProvider"/> classes that can receive authentication
8-
/// state information from the host environment.
7+
/// An interface implemented by services to receive authentication state information from the host environment.
8+
/// If this is implemented by the host's <see cref="AuthenticationStateProvider"/>, it will receive authentication state from the HttpContext.
9+
/// Or if this implemented service that is registered directly as an <see cref="IHostEnvironmentAuthenticationStateProvider"/>,
10+
/// it will receive the <see cref="AuthenticationState"/> returned by <see cref="AuthenticationStateProvider.GetAuthenticationStateAsync"/>
911
/// </summary>
1012
public interface IHostEnvironmentAuthenticationStateProvider
1113
{
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData
3+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.AuthenticationStateData() -> void
4+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.Claims.get -> System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string!, string!>>!
5+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.Claims.set -> void
6+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.NameClaimType.get -> string!
7+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.NameClaimType.set -> void
8+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.RoleClaimType.get -> string!
9+
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.RoleClaimType.set -> void

src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Reflection.Metadata;
66
using Microsoft.AspNetCore.Components;
7+
using Microsoft.AspNetCore.Components.Authorization;
78
using Microsoft.AspNetCore.Components.Endpoints;
89
using Microsoft.AspNetCore.Components.Endpoints.DependencyInjection;
910
using Microsoft.AspNetCore.Components.Endpoints.Forms;
1011
using Microsoft.AspNetCore.Components.Forms;
1112
using Microsoft.AspNetCore.Components.Forms.Mapping;
1213
using Microsoft.AspNetCore.Components.Infrastructure;
1314
using Microsoft.AspNetCore.Components.Routing;
15+
using Microsoft.AspNetCore.Components.Server;
1416
using Microsoft.AspNetCore.Components.Web;
1517
using Microsoft.AspNetCore.DataProtection;
1618
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -66,6 +68,7 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection
6668
ServiceDescriptor.Singleton<IPostConfigureOptions<RazorComponentsServiceOptions>, DefaultRazorComponentsServiceOptionsConfiguration>());
6769
services.TryAddScoped<EndpointRoutingStateProvider>();
6870
services.TryAddScoped<IRoutingStateProvider>(sp => sp.GetRequiredService<EndpointRoutingStateProvider>());
71+
services.TryAddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
6972
services.AddSupplyValueFromQueryProvider();
7073
services.TryAddCascadingValue(sp => sp.GetRequiredService<EndpointHtmlRenderer>().HttpContext);
7174

src/Components/Server/src/Circuits/ServerAuthenticationStateProvider.cs renamed to src/Components/Endpoints/src/DependencyInjection/ServerAuthenticationStateProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Server;
1010
/// </summary>
1111
public class ServerAuthenticationStateProvider : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider
1212
{
13-
private Task<AuthenticationState> _authenticationStateTask;
13+
private Task<AuthenticationState>? _authenticationStateTask;
1414

1515
/// <inheritdoc />
1616
public override Task<AuthenticationState> GetAuthenticationStateAsync()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
#nullable enable
22
Microsoft.AspNetCore.Components.Routing.RazorComponentsEndpointHttpContextExtensions
3+
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider
4+
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.ServerAuthenticationStateProvider() -> void
5+
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! authenticationStateTask) -> void
6+
override Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.GetAuthenticationStateAsync() -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>!
37
static Microsoft.AspNetCore.Components.Endpoints.Infrastructure.ComponentEndpointConventionBuilderHelper.GetEndpointRouteBuilder(Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder) -> Microsoft.AspNetCore.Routing.IEndpointRouteBuilder!
48
static Microsoft.AspNetCore.Components.Routing.RazorComponentsEndpointHttpContextExtensions.AcceptsInteractiveRouting(this Microsoft.AspNetCore.Http.HttpContext! context) -> bool

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,22 @@ internal static async Task InitializeStandardComponentServicesAsync(
7878
var navigationManager = (IHostEnvironmentNavigationManager)httpContext.RequestServices.GetRequiredService<NavigationManager>();
7979
navigationManager?.Initialize(GetContextBaseUri(httpContext.Request), GetFullUri(httpContext.Request));
8080

81-
if (httpContext.RequestServices.GetService<AuthenticationStateProvider>() is IHostEnvironmentAuthenticationStateProvider authenticationStateProvider)
81+
var authenticationStateProvider = httpContext.RequestServices.GetService<AuthenticationStateProvider>();
82+
if (authenticationStateProvider is IHostEnvironmentAuthenticationStateProvider hostEnvironmentAuthenticationStateProvider)
8283
{
8384
var authenticationState = new AuthenticationState(httpContext.User);
84-
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
85+
hostEnvironmentAuthenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
86+
}
87+
88+
if (authenticationStateProvider != null)
89+
{
90+
var authStateListeners = httpContext.RequestServices.GetServices<IHostEnvironmentAuthenticationStateProvider>();
91+
Task<AuthenticationState>? authStateTask = null;
92+
foreach (var authStateListener in authStateListeners)
93+
{
94+
authStateTask ??= authenticationStateProvider.GetAuthenticationStateAsync();
95+
authStateListener.SetAuthenticationState(authStateTask);
96+
}
8597
}
8698

8799
if (handler != null && form != null)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider))]

src/Components/Server/src/PublicAPI.Unshipped.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
#nullable enable
2+
*REMOVED*Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider
3+
*REMOVED*Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.ServerAuthenticationStateProvider() -> void
4+
*REMOVED*Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! authenticationStateTask) -> void
5+
*REMOVED*override Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.GetAuthenticationStateAsync() -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>!
6+
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
7+
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.ServerAuthenticationStateProvider() -> void (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
8+
Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! authenticationStateTask) -> void (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
29
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions
310
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ConfigureWebSocketAcceptContext.get -> System.Func<Microsoft.AspNetCore.Http.HttpContext!, Microsoft.AspNetCore.Http.WebSocketAcceptContext!, System.Threading.Tasks.Task!>?
411
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ConfigureWebSocketAcceptContext.set -> void
@@ -7,4 +14,5 @@ Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ContentSe
714
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.DisableWebSocketCompression.get -> bool
815
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.DisableWebSocketCompression.set -> void
916
Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions.ServerComponentsEndpointOptions() -> void
17+
override Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider.GetAuthenticationStateAsync() -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!>! (forwarded, contained in Microsoft.AspNetCore.Components.Endpoints)
1018
static Microsoft.AspNetCore.Builder.ServerRazorComponentsEndpointConventionBuilderExtensions.AddInteractiveServerRenderMode(this Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder, System.Action<Microsoft.AspNetCore.Components.Server.ServerComponentsEndpointOptions!>! configure) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder!
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using System.Security.Claims;
6+
using Microsoft.AspNetCore.Components.Authorization;
7+
8+
namespace Microsoft.AspNetCore.Components.WebAssembly.Server;
9+
10+
/// <summary>
11+
/// Provides options for configuring the JSON serialization of the <see cref="AuthenticationState"/> provided by the server's <see cref="AuthenticationStateProvider"/>
12+
/// to the WebAssembly client using <see cref="PersistentComponentState"/>.
13+
/// </summary>
14+
public class AuthenticationStateSerializationOptions
15+
{
16+
/// <summary>
17+
/// Default constructor for <see cref="AuthenticationStateSerializationOptions"/>.
18+
/// </summary>
19+
public AuthenticationStateSerializationOptions()
20+
{
21+
SerializationCallback = SerializeAuthenticationStateAsync;
22+
}
23+
24+
/// <summary>
25+
/// If <see langword="true"/>, the default <see cref="SerializationCallback"/> will serialize all claims; otherwise, it will only serialize
26+
/// the <see cref="ClaimsIdentity.NameClaimType"/> and <see cref="ClaimsIdentity.RoleClaimType"/> claims.
27+
/// </summary>
28+
public bool SerializeAllClaims { get; set; }
29+
30+
/// <summary>
31+
/// Default implementation for converting the server's <see cref="AuthenticationState"/> to an <see cref="AuthenticationStateData"/> object
32+
/// for JSON serialization to the client using <see cref="PersistentComponentState"/>."/>
33+
/// </summary>
34+
public Func<AuthenticationState, ValueTask<AuthenticationStateData?>> SerializationCallback { get; set; }
35+
36+
private ValueTask<AuthenticationStateData?> SerializeAuthenticationStateAsync(AuthenticationState authenticationState)
37+
{
38+
AuthenticationStateData? data = null;
39+
40+
if (authenticationState.User.Identity?.IsAuthenticated ?? false)
41+
{
42+
data = new AuthenticationStateData();
43+
44+
if (authenticationState.User.Identities.FirstOrDefault() is { } identity)
45+
{
46+
data.NameClaimType = identity.NameClaimType;
47+
data.RoleClaimType = identity.RoleClaimType;
48+
}
49+
50+
if (SerializeAllClaims)
51+
{
52+
foreach (var claim in authenticationState.User.Claims)
53+
{
54+
data.Claims.Add(new(claim.Type, claim.Value));
55+
}
56+
}
57+
else
58+
{
59+
if (authenticationState.User.FindFirst(data.NameClaimType) is { } nameClaim)
60+
{
61+
data.Claims.Add(new(nameClaim.Type, nameClaim.Value));
62+
}
63+
64+
foreach (var roleClaim in authenticationState.User.FindAll(data.RoleClaimType))
65+
{
66+
data.Claims.Add(new(roleClaim.Type, roleClaim.Value));
67+
}
68+
}
69+
}
70+
71+
return ValueTask.FromResult(data);
72+
}
73+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Components.Authorization;
5+
using Microsoft.AspNetCore.Components.Web;
6+
using Microsoft.Extensions.Options;
7+
8+
namespace Microsoft.AspNetCore.Components.WebAssembly.Server;
9+
10+
internal sealed class AuthenticationStateSerializer : IHostEnvironmentAuthenticationStateProvider, IDisposable
11+
{
12+
// Do not change. This must match all versions of the server-side DeserializedAuthenticationStateProvider.PersistenceKey.
13+
internal const string PersistenceKey = $"__internal__{nameof(AuthenticationState)}";
14+
15+
private readonly PersistentComponentState _state;
16+
private readonly Func<AuthenticationState, ValueTask<AuthenticationStateData?>> _serializeCallback;
17+
private readonly PersistingComponentStateSubscription _subscription;
18+
19+
private Task<AuthenticationState>? _authenticationStateTask;
20+
21+
public AuthenticationStateSerializer(PersistentComponentState persistentComponentState, IOptions<AuthenticationStateSerializationOptions> options)
22+
{
23+
_state = persistentComponentState;
24+
_serializeCallback = options.Value.SerializationCallback;
25+
_subscription = persistentComponentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);
26+
}
27+
28+
private async Task OnPersistingAsync()
29+
{
30+
if (_authenticationStateTask is null)
31+
{
32+
throw new InvalidOperationException($"{nameof(SetAuthenticationState)} must be called before the {nameof(PersistentComponentState)}.{nameof(PersistentComponentState.RegisterOnPersisting)} callback.");
33+
}
34+
35+
var authenticationStateData = await _serializeCallback(await _authenticationStateTask);
36+
if (authenticationStateData is not null)
37+
{
38+
_state.PersistAsJson(PersistenceKey, authenticationStateData);
39+
}
40+
}
41+
42+
/// <inheritdoc />
43+
public void SetAuthenticationState(Task<AuthenticationState> authenticationStateTask)
44+
{
45+
_authenticationStateTask = authenticationStateTask ?? throw new ArgumentNullException(nameof(authenticationStateTask));
46+
}
47+
48+
public void Dispose()
49+
{
50+
_subscription.Dispose();
51+
}
52+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions
3+
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.AuthenticationStateSerializationOptions() -> void
4+
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializationCallback.get -> System.Func<Microsoft.AspNetCore.Components.Authorization.AuthenticationState!, System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData?>>!
5+
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializationCallback.set -> void
6+
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializeAllClaims.get -> bool
7+
Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.SerializeAllClaims.set -> void
28
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.ServeMultithreadingHeaders.get -> bool
39
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.ServeMultithreadingHeaders.set -> void
410
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.StaticAssetsManifestPath.get -> string?
511
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.StaticAssetsManifestPath.set -> void
12+
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!

src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
using Microsoft.AspNetCore.Builder;
55
using Microsoft.AspNetCore.Components;
6+
using Microsoft.AspNetCore.Components.Authorization;
67
using Microsoft.AspNetCore.Components.Endpoints.Infrastructure;
78
using Microsoft.AspNetCore.Components.Web;
9+
using Microsoft.AspNetCore.Components.WebAssembly.Server;
810
using Microsoft.AspNetCore.Http;
911
using Microsoft.AspNetCore.Routing;
1012
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -30,6 +32,25 @@ public static IRazorComponentsBuilder AddInteractiveWebAssemblyComponents(this I
3032
return builder;
3133
}
3234

35+
/// <summary>
36+
/// Serializes the <see cref="AuthenticationState"/> returned by the server-side <see cref="AuthenticationStateProvider"/> using <see cref="PersistentComponentState"/>
37+
/// for use by interactive WebAssembly components via a deserializing client-side <see cref="AuthenticationStateProvider"/> which can be added by calling
38+
/// AddAuthenticationStateDeserialization from the Microsoft.AspNetCore.Components.WebAssembly.Authentication package in the client project.
39+
/// </summary>
40+
/// <param name="builder">The <see cref="IRazorComponentsBuilder"/>.</param>
41+
/// <param name="configure">A callback to customize the serialization of the <see cref="AuthenticationState"/>.</param>
42+
/// <returns>An <see cref="IRazorComponentsBuilder"/> that can be used to further customize the configuration.</returns>
43+
public static IRazorComponentsBuilder AddAuthenticationStateSerialization(this IRazorComponentsBuilder builder, Action<AuthenticationStateSerializationOptions>? configure = null)
44+
{
45+
builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped<IHostEnvironmentAuthenticationStateProvider, AuthenticationStateSerializer>());
46+
if (configure is not null)
47+
{
48+
builder.Services.Configure(configure);
49+
}
50+
51+
return builder;
52+
}
53+
3354
private class WebAssemblyEndpointProvider(IServiceProvider services) : RenderModeEndpointProvider
3455
{
3556
public override IEnumerable<RouteEndpointBuilder> GetEndpointBuilders(IComponentRenderMode renderMode, IApplicationBuilder applicationBuilder)

0 commit comments

Comments
 (0)