Skip to content

Commit d4430f0

Browse files
authored
Add MapIdentityApi<TUser>() (#47414)
1 parent 0db29cc commit d4430f0

Some content is hidden

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

41 files changed

+1069
-14
lines changed

AspNetCore.sln

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1762,7 +1762,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
17621762
EndProject
17631763
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests", "src\Servers\Kestrel\Transport.NamedPipes\test\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests.csproj", "{97C7D2A4-87E5-4A4A-A170-D736427D5C21}"
17641764
EndProject
1765-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.RequestDelegateGenerator", "src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
1765+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.RequestDelegateGenerator", "src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
17661766
EndProject
17671767
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QuickGrid", "QuickGrid", "{C406D9E0-1585-43F9-AA8F-D468AF84A996}"
17681768
EndProject
@@ -1780,6 +1780,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
17801780
EndProject
17811781
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorUnitedApp", "src\Components\Samples\BlazorUnitedApp\BlazorUnitedApp.csproj", "{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}"
17821782
EndProject
1783+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BearerToken", "BearerToken", "{56291265-B7BF-4756-92AB-FC30F09381D1}"
1784+
EndProject
1785+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.BearerToken", "src\Security\Authentication\BearerToken\src\Microsoft.AspNetCore.Authentication.BearerToken.csproj", "{66FA1041-5556-43A0-9CA3-F9937F085F6E}"
1786+
EndProject
1787+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.ApiEndpoints", "src\Identity\samples\IdentitySample.ApiEndpoints\IdentitySample.ApiEndpoints.csproj", "{37FC77EA-AC44-4D08-B002-8EFF415C424A}"
1788+
EndProject
17831789
Global
17841790
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17851791
Debug|Any CPU = Debug|Any CPU
@@ -10701,6 +10707,38 @@ Global
1070110707
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}.Release|x64.Build.0 = Release|Any CPU
1070210708
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}.Release|x86.ActiveCfg = Release|Any CPU
1070310709
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}.Release|x86.Build.0 = Release|Any CPU
10710+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10711+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
10712+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|arm64.ActiveCfg = Debug|Any CPU
10713+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|arm64.Build.0 = Debug|Any CPU
10714+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|x64.ActiveCfg = Debug|Any CPU
10715+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|x64.Build.0 = Debug|Any CPU
10716+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|x86.ActiveCfg = Debug|Any CPU
10717+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Debug|x86.Build.0 = Debug|Any CPU
10718+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
10719+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|Any CPU.Build.0 = Release|Any CPU
10720+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|arm64.ActiveCfg = Release|Any CPU
10721+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|arm64.Build.0 = Release|Any CPU
10722+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|x64.ActiveCfg = Release|Any CPU
10723+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|x64.Build.0 = Release|Any CPU
10724+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|x86.ActiveCfg = Release|Any CPU
10725+
{66FA1041-5556-43A0-9CA3-F9937F085F6E}.Release|x86.Build.0 = Release|Any CPU
10726+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10727+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|Any CPU.Build.0 = Debug|Any CPU
10728+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|arm64.ActiveCfg = Debug|Any CPU
10729+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|arm64.Build.0 = Debug|Any CPU
10730+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|x64.ActiveCfg = Debug|Any CPU
10731+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|x64.Build.0 = Debug|Any CPU
10732+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|x86.ActiveCfg = Debug|Any CPU
10733+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Debug|x86.Build.0 = Debug|Any CPU
10734+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|Any CPU.ActiveCfg = Release|Any CPU
10735+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|Any CPU.Build.0 = Release|Any CPU
10736+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|arm64.ActiveCfg = Release|Any CPU
10737+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|arm64.Build.0 = Release|Any CPU
10738+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|x64.ActiveCfg = Release|Any CPU
10739+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|x64.Build.0 = Release|Any CPU
10740+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|x86.ActiveCfg = Release|Any CPU
10741+
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|x86.Build.0 = Release|Any CPU
1070410742
EndGlobalSection
1070510743
GlobalSection(SolutionProperties) = preSolution
1070610744
HideSolutionNode = FALSE
@@ -11580,6 +11618,9 @@ Global
1158011618
{AE4D272D-6F13-42C8-9404-C149188AFA33} = {7BAEB9BF-28F4-4DFD-9A04-E5193683C261}
1158111619
{5D438258-CB19-4282-814F-974ABBC71411} = {7BAEB9BF-28F4-4DFD-9A04-E5193683C261}
1158211620
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E} = {5FE1FBC1-8CE3-4355-9866-44FE1307C5F1}
11621+
{56291265-B7BF-4756-92AB-FC30F09381D1} = {822D1519-77F0-484A-B9AB-F694C2CC25F1}
11622+
{66FA1041-5556-43A0-9CA3-F9937F085F6E} = {56291265-B7BF-4756-92AB-FC30F09381D1}
11623+
{37FC77EA-AC44-4D08-B002-8EFF415C424A} = {64B2A28F-6D82-4F2B-B0BB-88DE5216DD2C}
1158311624
EndGlobalSection
1158411625
GlobalSection(ExtensibilityGlobals) = postSolution
1158511626
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

eng/ProjectReferences.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.NamedPipes\src\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj" />
5454
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Quic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj" />
5555
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" />
56+
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.BearerToken" ProjectPath="$(RepoRoot)src\Security\Authentication\BearerToken\src\Microsoft.AspNetCore.Authentication.BearerToken.csproj" />
5657
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Certificate" ProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\src\Microsoft.AspNetCore.Authentication.Certificate.csproj" />
5758
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Cookies" ProjectPath="$(RepoRoot)src\Security\Authentication\Cookies\src\Microsoft.AspNetCore.Authentication.Cookies.csproj" />
5859
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication" ProjectPath="$(RepoRoot)src\Security\Authentication\Core\src\Microsoft.AspNetCore.Authentication.csproj" />

eng/SharedFramework.Local.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" />
6464
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
6565
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
66+
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Authentication.BearerToken" />
6667
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Authentication.Cookies" />
6768
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Authentication" />
6869
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Authentication.OAuth" />

eng/TrimmableProjects.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
<TrimmableProject Include="Microsoft.AspNetCore.Routing" />
3333
<TrimmableProject Include="Microsoft.AspNetCore.WebUtilities" />
3434
<TrimmableProject Include="Microsoft.AspNetCore.Html.Abstractions" />
35-
<TrimmableProject Include="Microsoft.AspNetCore.Identity" />
3635
<TrimmableProject Include="Microsoft.Extensions.Identity.Core" />
3736
<TrimmableProject Include="Microsoft.Extensions.Identity.Stores" />
3837
<TrimmableProject Include="Microsoft.AspNetCore.Connections.Abstractions" />
@@ -44,6 +43,7 @@
4443
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" />
4544
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
4645
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
46+
<TrimmableProject Include="Microsoft.AspNetCore.Authentication.BearerToken" />
4747
<TrimmableProject Include="Microsoft.AspNetCore.Authentication.Certificate" />
4848
<TrimmableProject Include="Microsoft.AspNetCore.Authentication.Cookies" />
4949
<TrimmableProject Include="Microsoft.AspNetCore.Authentication" />

src/Framework/test/TestData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ static TestData()
2222
"Microsoft.AspNetCore.Antiforgery",
2323
"Microsoft.AspNetCore.Authentication",
2424
"Microsoft.AspNetCore.Authentication.Abstractions",
25+
"Microsoft.AspNetCore.Authentication.BearerToken",
2526
"Microsoft.AspNetCore.Authentication.Cookies",
2627
"Microsoft.AspNetCore.Authentication.Core",
2728
"Microsoft.AspNetCore.Authentication.OAuth",
@@ -168,6 +169,7 @@ static TestData()
168169
{
169170
{ "Microsoft.AspNetCore.Antiforgery" },
170171
{ "Microsoft.AspNetCore.Authentication.Abstractions" },
172+
{ "Microsoft.AspNetCore.Authentication.BearerToken" },
171173
{ "Microsoft.AspNetCore.Authentication.Cookies" },
172174
{ "Microsoft.AspNetCore.Authentication.Core" },
173175
{ "Microsoft.AspNetCore.Authentication.OAuth" },
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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.Text.Json.Serialization;
5+
6+
namespace Microsoft.AspNetCore.Identity.DTO;
7+
8+
[JsonSerializable(typeof(RegisterRequest))]
9+
[JsonSerializable(typeof(LoginRequest))]
10+
internal sealed partial class IdentityEndpointsJsonSerializerContext : JsonSerializerContext
11+
{
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
namespace Microsoft.AspNetCore.Identity.DTO;
5+
6+
internal sealed class LoginRequest
7+
{
8+
public required string Username { get; init; }
9+
public required string Password { get; init; }
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
namespace Microsoft.AspNetCore.Identity.DTO;
5+
6+
internal sealed class RegisterRequest
7+
{
8+
public required string Username { get; init; }
9+
public required string Password { get; init; }
10+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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.Diagnostics.CodeAnalysis;
5+
using System.Linq;
6+
using Microsoft.AspNetCore.Authentication.BearerToken.DTO;
7+
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.Http.HttpResults;
10+
using Microsoft.AspNetCore.Http.Metadata;
11+
using Microsoft.AspNetCore.Identity;
12+
using Microsoft.AspNetCore.Identity.DTO;
13+
using Microsoft.Extensions.DependencyInjection;
14+
15+
namespace Microsoft.AspNetCore.Routing;
16+
17+
/// <summary>
18+
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add identity endpoints.
19+
/// </summary>
20+
public static class IdentityApiEndpointRouteBuilderExtensions
21+
{
22+
/// <summary>
23+
/// Add endpoints for registering, logging in, and logging out using ASP.NET Core Identity.
24+
/// </summary>
25+
/// <typeparam name="TUser">The type describing the user. This should match the generic parameter in <see cref="UserManager{TUser}"/>.</typeparam>
26+
/// <param name="endpoints">
27+
/// The <see cref="IEndpointRouteBuilder"/> to add the identity endpoints to.
28+
/// Call <see cref="EndpointRouteBuilderExtensions.MapGroup(IEndpointRouteBuilder, string)"/> to add a prefix to all the endpoints.
29+
/// </param>
30+
/// <returns>An <see cref="IEndpointConventionBuilder"/> to further customize the added endpoints.</returns>
31+
// TODO: Remove RequiresDynamicCode when https://github.com/dotnet/aspnetcore/issues/47918 is fixed and RDG is enabled.
32+
[RequiresDynamicCode("This API requires generated code that is not compatible with native AOT applications.")]
33+
public static IEndpointConventionBuilder MapIdentityApi<TUser>(this IEndpointRouteBuilder endpoints) where TUser : class, new()
34+
{
35+
ArgumentNullException.ThrowIfNull(endpoints);
36+
37+
var routeGroup = endpoints.MapGroup("");
38+
39+
// NOTE: We cannot inject UserManager<TUser> directly because the TUser generic parameter is currently unsupported by RDG.
40+
// https://github.com/dotnet/aspnetcore/issues/47338
41+
routeGroup.MapPost("/register", async Task<Results<Ok, ValidationProblem>>
42+
([FromBody] RegisterRequest registration, [FromServices] IServiceProvider services) =>
43+
{
44+
var userManager = services.GetRequiredService<UserManager<TUser>>();
45+
46+
var user = new TUser();
47+
await userManager.SetUserNameAsync(user, registration.Username);
48+
var result = await userManager.CreateAsync(user, registration.Password);
49+
50+
if (result.Succeeded)
51+
{
52+
return TypedResults.Ok();
53+
}
54+
55+
return TypedResults.ValidationProblem(result.Errors.ToDictionary(e => e.Code, e => new[] { e.Description }));
56+
});
57+
58+
routeGroup.MapPost("/login", async Task<Results<UnauthorizedHttpResult, Ok<AccessTokenResponse>, SignInHttpResult>>
59+
([FromBody] LoginRequest login, [FromQuery] bool? cookieMode, [FromServices] IServiceProvider services) =>
60+
{
61+
var userManager = services.GetRequiredService<UserManager<TUser>>();
62+
var user = await userManager.FindByNameAsync(login.Username);
63+
64+
if (user is null || !await userManager.CheckPasswordAsync(user, login.Password))
65+
{
66+
return TypedResults.Unauthorized();
67+
}
68+
69+
var claimsFactory = services.GetRequiredService<IUserClaimsPrincipalFactory<TUser>>();
70+
var claimsPrincipal = await claimsFactory.CreateAsync(user);
71+
72+
var useCookies = cookieMode ?? false;
73+
var scheme = useCookies ? IdentityConstants.ApplicationScheme : IdentityConstants.BearerScheme;
74+
75+
return TypedResults.SignIn(claimsPrincipal, authenticationScheme: scheme);
76+
});
77+
78+
return new IdentityEndpointsConventionBuilder(routeGroup);
79+
}
80+
81+
// Wrap RouteGroupBuilder with a non-public type to avoid a potential future behavioral breaking change.
82+
private sealed class IdentityEndpointsConventionBuilder(RouteGroupBuilder inner) : IEndpointConventionBuilder
83+
{
84+
#pragma warning disable CA1822 // Mark members as static False positive reported by https://github.com/dotnet/roslyn-analyzers/issues/6573
85+
private IEndpointConventionBuilder InnerAsConventionBuilder => inner;
86+
#pragma warning restore CA1822 // Mark members as static
87+
88+
public void Add(Action<EndpointBuilder> convention) => InnerAsConventionBuilder.Add(convention);
89+
public void Finally(Action<EndpointBuilder> finallyConvention) => InnerAsConventionBuilder.Finally(finallyConvention);
90+
}
91+
92+
[AttributeUsage(AttributeTargets.Parameter)]
93+
private sealed class FromBodyAttribute : Attribute, IFromBodyMetadata
94+
{
95+
}
96+
97+
[AttributeUsage(AttributeTargets.Parameter)]
98+
private sealed class FromServicesAttribute : Attribute, IFromServiceMetadata
99+
{
100+
}
101+
102+
[AttributeUsage(AttributeTargets.Parameter)]
103+
private sealed class FromQueryAttribute : Attribute, IFromQueryMetadata
104+
{
105+
public string? Name => null;
106+
}
107+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.Authentication;
5+
using Microsoft.AspNetCore.Authentication.BearerToken;
6+
using Microsoft.AspNetCore.Http.Json;
7+
using Microsoft.AspNetCore.Routing;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.DependencyInjection.Extensions;
10+
using Microsoft.Extensions.Options;
11+
12+
namespace Microsoft.AspNetCore.Identity;
13+
14+
/// <summary>
15+
/// <see cref="IdentityBuilder"/> extension methods to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/>.
16+
/// </summary>
17+
public static class IdentityApiEndpointsIdentityBuilderExtensions
18+
{
19+
/// <summary>
20+
/// Adds configuration ans services needed to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/>
21+
/// but does not configure authentication. Call <see cref="BearerTokenExtensions.AddBearerToken(AuthenticationBuilder, Action{BearerTokenOptions}?)"/> and/or
22+
/// <see cref="IdentityCookieAuthenticationBuilderExtensions.AddIdentityCookies(AuthenticationBuilder)"/> to configure authentication separately.
23+
/// </summary>
24+
/// <param name="builder">The <see cref="IdentityBuilder"/>.</param>
25+
/// <returns>The <see cref="IdentityBuilder"/>.</returns>
26+
public static IdentityBuilder AddApiEndpoints(this IdentityBuilder builder)
27+
{
28+
ArgumentNullException.ThrowIfNull(builder);
29+
30+
builder.AddSignInManager();
31+
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<JsonOptions>, IdentityEndpointsJsonOptionsSetup>());
32+
return builder;
33+
}
34+
}

0 commit comments

Comments
 (0)