Skip to content

Commit 5d6de34

Browse files
committed
Preserve RemoteAuthenticationContext during JS interop (#54225)
1 parent a871e2d commit 5d6de34

File tree

18 files changed

+354
-5
lines changed

18 files changed

+354
-5
lines changed

AspNetCore.sln

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,6 +1782,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Output
17821782
EndProject
17831783
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotReferencedInWasmCodePackage", "src\Components\test\testassets\NotReferencedInWasmCodePackage\NotReferencedInWasmCodePackage.csproj", "{433F91E4-E39D-4EB0-B798-2998B3969A2C}"
17841784
EndProject
1785+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components.WasmRemoteAuthentication", "src\Components\test\testassets\Components.WasmRemoteAuthentication\Components.WasmRemoteAuthentication.csproj", "{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}"
1786+
EndProject
17851787
Global
17861788
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17871789
Debug|Any CPU = Debug|Any CPU
@@ -10735,6 +10737,22 @@ Global
1073510737
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|x64.Build.0 = Release|Any CPU
1073610738
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|x86.ActiveCfg = Release|Any CPU
1073710739
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|x86.Build.0 = Release|Any CPU
10740+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10741+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|Any CPU.Build.0 = Debug|Any CPU
10742+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|arm64.ActiveCfg = Debug|Any CPU
10743+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|arm64.Build.0 = Debug|Any CPU
10744+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|x64.ActiveCfg = Debug|Any CPU
10745+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|x64.Build.0 = Debug|Any CPU
10746+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|x86.ActiveCfg = Debug|Any CPU
10747+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Debug|x86.Build.0 = Debug|Any CPU
10748+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|Any CPU.ActiveCfg = Release|Any CPU
10749+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|Any CPU.Build.0 = Release|Any CPU
10750+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|arm64.ActiveCfg = Release|Any CPU
10751+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|arm64.Build.0 = Release|Any CPU
10752+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x64.ActiveCfg = Release|Any CPU
10753+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x64.Build.0 = Release|Any CPU
10754+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x86.ActiveCfg = Release|Any CPU
10755+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x86.Build.0 = Release|Any CPU
1073810756
EndGlobalSection
1073910757
GlobalSection(SolutionProperties) = preSolution
1074010758
HideSolutionNode = FALSE
@@ -11615,6 +11633,7 @@ Global
1161511633
{A939893A-B3CD-48F6-80D3-340C8A6E275B} = {AA5ABFBC-177C-421E-B743-005E0FD1248B}
1161611634
{F232B503-D412-45EE-8B31-EFD46B9FA302} = {AA5ABFBC-177C-421E-B743-005E0FD1248B}
1161711635
{433F91E4-E39D-4EB0-B798-2998B3969A2C} = {6126DCE4-9692-4EE2-B240-C65743572995}
11636+
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13} = {6126DCE4-9692-4EE2-B240-C65743572995}
1161811637
EndGlobalSection
1161911638
GlobalSection(ExtensibilityGlobals) = postSolution
1162011639
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

src/Components/Components.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"src\\Components\\test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
5353
"src\\Components\\test\\testassets\\Components.TestServer\\Components.TestServer.csproj",
5454
"src\\Components\\test\\testassets\\Components.WasmMinimal\\Components.WasmMinimal.csproj",
55+
"src\\Components\\test\\testassets\\Components.WasmRemoteAuthentication\\Components.WasmRemoteAuthentication.csproj",
5556
"src\\Components\\test\\testassets\\ComponentsApp.App\\ComponentsApp.App.csproj",
5657
"src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
5758
"src\\Components\\test\\testassets\\GlobalizationWasmApp\\GlobalizationWasmApp.csproj",

src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public virtual async Task<RemoteAuthenticationResult<TRemoteAuthenticationState>
108108
RemoteAuthenticationContext<TRemoteAuthenticationState> context)
109109
{
110110
await EnsureAuthService();
111-
var result = await JsRuntime.InvokeAsync<RemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.signIn", context);
111+
var result = await JSInvokeWithContextAsync<RemoteAuthenticationContext<TRemoteAuthenticationState>, RemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.signIn", context);
112112
await UpdateUserOnSuccess(result);
113113

114114
return result;
@@ -130,7 +130,7 @@ public virtual async Task<RemoteAuthenticationResult<TRemoteAuthenticationState>
130130
RemoteAuthenticationContext<TRemoteAuthenticationState> context)
131131
{
132132
await EnsureAuthService();
133-
var result = await JsRuntime.InvokeAsync<RemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.signOut", context);
133+
var result = await JSInvokeWithContextAsync<RemoteAuthenticationContext<TRemoteAuthenticationState>, RemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.signOut", context);
134134
await UpdateUserOnSuccess(result);
135135

136136
return result;
@@ -187,6 +187,11 @@ public virtual async ValueTask<AccessTokenResult> RequestAccessToken(AccessToken
187187
} : null);
188188
}
189189

190+
// JSRuntime.InvokeAsync does not properly annotate all arguments with DynamicallyAccessedMembersAttribute. https://github.com/dotnet/aspnetcore/issues/39839
191+
// Calling JsRuntime.InvokeAsync directly results allows the RemoteAuthenticationContext.State getter to be trimmed. https://github.com/dotnet/aspnetcore/issues/49956
192+
private ValueTask<TResult> JSInvokeWithContextAsync<[DynamicallyAccessedMembers(JsonSerialized)] TContext, [DynamicallyAccessedMembers(JsonSerialized)] TResult>(
193+
string identifier, TContext context) => JsRuntime.InvokeAsync<TResult>(identifier, context);
194+
190195
private string GetReturnUrl(string? customReturnUrl) =>
191196
customReturnUrl != null ? Navigation.ToAbsoluteUri(customReturnUrl).AbsoluteUri : Navigation.Uri;
192197

src/Components/test/E2ETest/Infrastructure/ServerFixtures/AspNetSiteServerFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protected override IHost CreateWebHost()
3232
}
3333

3434
var assembly = ApplicationAssembly ?? BuildWebHostMethod.Method.DeclaringType.Assembly;
35-
var sampleSitePath = DefaultGetContentRoot(assembly);
35+
var sampleSitePath = GetContentRootMethod(assembly);
3636

3737
var host = "127.0.0.1";
3838
if (E2ETestOptions.Instance.SauceTest)

src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<!--
44
Skip building and running the Components E2E tests in CI unless explicitly configured otherwise via
@@ -85,6 +85,11 @@
8585
Include="..\..\WebAssembly\testassets\Wasm.Prerendered.Server\Wasm.Prerendered.Server.csproj"
8686
Targets="Build;Publish"
8787
Properties="BuildProjectReferences=false;TestTrimmedApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed\Wasm.Prerendered.Server\;" />
88+
89+
<ProjectReference
90+
Include="..\testassets\Components.TestServer\Components.TestServer.csproj"
91+
Targets="Build;Publish"
92+
Properties="BuildProjectReferences=false;TestTrimmedOrMultithreadingApps=true;PublishDir=$(MSBuildThisFileDirectory)$(OutputPath)trimmed-or-threading\Components.TestServer\;" />
8893
</ItemGroup>
8994

9095
<!-- Shared testing infrastructure for running E2E tests using selenium -->
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.Reflection;
5+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
6+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
7+
using Microsoft.AspNetCore.E2ETesting;
8+
using Microsoft.AspNetCore.Hosting;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Hosting;
11+
using Microsoft.Extensions.Logging;
12+
using Microsoft.Extensions.Logging.Testing;
13+
using OpenQA.Selenium;
14+
using TestServer;
15+
using Xunit.Abstractions;
16+
17+
namespace Microsoft.AspNetCore.Components.E2ETest.Tests;
18+
19+
public class RemoteAuthenticationTest :
20+
ServerTestBase<BasicTestAppServerSiteFixture<RemoteAuthenticationStartup>>
21+
{
22+
public readonly bool TestTrimmedApps = typeof(ToggleExecutionModeServerFixture<>).Assembly
23+
.GetCustomAttributes<AssemblyMetadataAttribute>()
24+
.First(m => m.Key == "Microsoft.AspNetCore.E2ETesting.TestTrimmedOrMultithreadingApps")
25+
.Value == "true";
26+
27+
public RemoteAuthenticationTest(
28+
BrowserFixture browserFixture,
29+
BasicTestAppServerSiteFixture<RemoteAuthenticationStartup> serverFixture,
30+
ITestOutputHelper output)
31+
: base(browserFixture, serverFixture, output)
32+
{
33+
serverFixture.ApplicationAssembly = typeof(RemoteAuthenticationStartup).Assembly;
34+
35+
if (TestTrimmedApps)
36+
{
37+
serverFixture.BuildWebHostMethod = BuildPublishedWebHost;
38+
serverFixture.GetContentRootMethod = GetPublishedContentRoot;
39+
}
40+
}
41+
42+
[Fact]
43+
public void NavigateToLogin_PreservesExtraQueryParams()
44+
{
45+
// If the preservedExtraQueryParams passed to NavigateToLogin by RedirectToLogin gets trimmed,
46+
// the OIDC endpoints will fail to authenticate the user.
47+
Navigate("/subdir/test-remote-authentication");
48+
49+
var heading = Browser.Exists(By.TagName("h1"));
50+
Browser.Equal("Hello, Jane Doe!", () => heading.Text);
51+
}
52+
53+
private static IHost BuildPublishedWebHost(string[] args) =>
54+
Host.CreateDefaultBuilder(args)
55+
.ConfigureLogging((ctx, lb) =>
56+
{
57+
TestSink sink = new TestSink();
58+
lb.AddProvider(new TestLoggerProvider(sink));
59+
lb.Services.AddSingleton(sink);
60+
})
61+
.ConfigureWebHostDefaults(webHostBuilder =>
62+
{
63+
webHostBuilder.UseStartup<RemoteAuthenticationStartup>();
64+
// Avoid UseStaticAssets or we won't use the trimmed published output.
65+
})
66+
.Build();
67+
68+
private static string GetPublishedContentRoot(Assembly assembly)
69+
{
70+
var contentRoot = Path.Combine(AppContext.BaseDirectory, "trimmed-or-threading", assembly.GetName().Name);
71+
72+
if (!Directory.Exists(contentRoot))
73+
{
74+
throw new DirectoryNotFoundException($"Test is configured to use trimmed outputs, but trimmed outputs were not found in {contentRoot}.");
75+
}
76+
77+
return contentRoot;
78+
}
79+
}

src/Components/test/E2ETest/Tests/WebAssemblyPrerenderedTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ private void WaitUntilLoaded()
5656

5757
private static string GetPublishedContentRoot(Assembly assembly)
5858
{
59-
var contentRoot = Path.Combine(AppContext.BaseDirectory, "trimmed", assembly.GetName().Name);
59+
var contentRoot = Path.Combine(AppContext.BaseDirectory, "trimmed-or-threading", assembly.GetName().Name);
6060

6161
if (!Directory.Exists(contentRoot))
6262
{

src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
<Reference Include="Microsoft.AspNetCore.SignalR" />
2626
<Reference Include="Microsoft.AspNetCore.Testing" />
2727
<Reference Include="Microsoft.Extensions.Hosting" />
28+
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" />
2829
</ItemGroup>
2930

3031
<ItemGroup>
3132
<ProjectReference Include="..\BasicTestApp\BasicTestApp.csproj" />
3233
<ProjectReference Include="..\Components.WasmMinimal\Components.WasmMinimal.csproj" />
34+
<ProjectReference Include="..\Components.WasmRemoteAuthentication\Components.WasmRemoteAuthentication.csproj" />
3335
</ItemGroup>
3436

3537
<ItemGroup>

src/Components/test/testassets/Components.TestServer/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public static async Task Main(string[] args)
1919
var createIndividualHosts = new Dictionary<string, (IHost host, string basePath)>
2020
{
2121
["Client authentication"] = (BuildWebHost<AuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"),
22+
["Remote client authentication"] = (BuildWebHost<RemoteAuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"),
2223
["Server authentication"] = (BuildWebHost<ServerAuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"),
2324
["CORS (WASM)"] = (BuildWebHost<CorsStartup>(CreateAdditionalArgs(args)), "/subdir"),
2425
["Prerendering (Server-side)"] = (BuildWebHost<PrerenderedStartup>(CreateAdditionalArgs(args)), "/prerendered"),
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<base href="/subdir/" />
7+
8+
<HeadOutlet @rendermode="new InteractiveWebAssemblyRenderMode(prerender: false)" />
9+
</head>
10+
11+
<body>
12+
<Components.WasmRemoteAuthentication.Routes @rendermode="new InteractiveWebAssemblyRenderMode(prerender: false)" />
13+
<script src="_framework/blazor.web.js" autostart="false"></script>
14+
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
15+
<script>
16+
Blazor.start({
17+
webAssembly: {
18+
loadBootResource: (type, name, defaultUri, integrity) => `WasmRemoteAuthentication/_framework/${name}`
19+
}
20+
});
21+
</script>
22+
</body>
23+
24+
</html>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.Globalization;
5+
using System.Reflection;
6+
using Components.TestServer.RazorComponents;
7+
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.IdentityModel.JsonWebTokens;
9+
using Microsoft.IdentityModel.Tokens;
10+
11+
namespace TestServer;
12+
13+
public class RemoteAuthenticationStartup
14+
{
15+
public void ConfigureServices(IServiceCollection services)
16+
{
17+
services.AddRazorComponents()
18+
.AddInteractiveWebAssemblyComponents();
19+
}
20+
21+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
22+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
23+
{
24+
app.Map("/subdir", app =>
25+
{
26+
app.UseStaticFiles();
27+
app.UseRouting();
28+
app.UseAntiforgery();
29+
app.UseEndpoints(endpoints =>
30+
{
31+
endpoints.MapRazorComponents<RemoteAuthenticationApp>()
32+
.AddAdditionalAssemblies(Assembly.Load("Components.WasmRemoteAuthentication"))
33+
.AddInteractiveWebAssemblyRenderMode(options => options.PathPrefix = "/WasmRemoteAuthentication");
34+
35+
var oidcEndpoints = endpoints.MapGroup("oidc");
36+
37+
// This is designed to test a single login at a time.
38+
var issuer = "";
39+
oidcEndpoints.MapGet(".well-known/openid-configuration", (HttpRequest request, [FromHeader] string host) =>
40+
{
41+
issuer = $"{(request.IsHttps ? "https" : "http")}://{host}";
42+
return Results.Json(new
43+
{
44+
issuer,
45+
authorization_endpoint = $"{issuer}/subdir/oidc/authorize",
46+
token_endpoint = $"{issuer}/subdir/oidc/token",
47+
});
48+
});
49+
50+
var lastCode = "";
51+
oidcEndpoints.MapGet("authorize", (string redirect_uri, string? state, string? prompt, bool? preservedExtraQueryParams) =>
52+
{
53+
// Require interaction so silent sign-in does not skip RedirectToLogin.razor.
54+
if (prompt == "none")
55+
{
56+
return Results.Redirect($"{redirect_uri}?error=interaction_required&state={state}");
57+
}
58+
59+
// Verify that the extra query parameters added by RedirectToLogin.razor are preserved.
60+
if (preservedExtraQueryParams != true)
61+
{
62+
return Results.Redirect($"{redirect_uri}?error=invalid_request&error_description=extraQueryParams%20not%20preserved&state={state}");
63+
}
64+
65+
lastCode = Random.Shared.Next().ToString(CultureInfo.InvariantCulture);
66+
return Results.Redirect($"{redirect_uri}?code={lastCode}&state={state}");
67+
});
68+
69+
var jwtHandler = new JsonWebTokenHandler();
70+
oidcEndpoints.MapPost("token", ([FromForm] string code) =>
71+
{
72+
if (string.IsNullOrEmpty(lastCode) && code != lastCode)
73+
{
74+
return Results.BadRequest("Bad code");
75+
}
76+
77+
return Results.Json(new
78+
{
79+
token_type = "Bearer",
80+
scope = "openid profile",
81+
expires_in = 3600,
82+
id_token = jwtHandler.CreateToken(new SecurityTokenDescriptor
83+
{
84+
Issuer = issuer,
85+
Audience = "s6BhdRkqt3",
86+
Claims = new Dictionary<string, object>
87+
{
88+
["sub"] = "248289761001",
89+
["name"] = "Jane Doe",
90+
},
91+
}),
92+
});
93+
}).DisableAntiforgery();
94+
});
95+
});
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<StaticWebAssetBasePath>WasmRemoteAuthentication</StaticWebAssetBasePath>
8+
</PropertyGroup>
9+
10+
<PropertyGroup Condition="'$(TestTrimmedOrMultithreadingApps)' == 'true'">
11+
<!-- Avoid spending time brotli compression publish output.-->
12+
<_BlazorBrotliCompressionLevel>NoCompression</_BlazorBrotliCompressionLevel>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly" />
17+
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" />
18+
</ItemGroup>
19+
20+
</Project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@page "/authentication/{action}"
2+
3+
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
4+
5+
<RemoteAuthenticatorView Action="@Action" />
6+
7+
@code {
8+
[Parameter] public string? Action { get; set; }
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@page "/test-remote-authentication"
2+
3+
@using Microsoft.AspNetCore.Components.Authorization
4+
5+
<AuthorizeView>
6+
<Authorized>
7+
<h1>Hello, @context.User.Identity?.Name!</h1>
8+
</Authorized>
9+
<NotAuthorized>
10+
@* Do this rather than rely on the [Authorize] attribute to avoid endpoint routing. *@
11+
<RedirectToLogin />
12+
</NotAuthorized>
13+
</AuthorizeView>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
2+
3+
var builder = WebAssemblyHostBuilder.CreateDefault(args);
4+
5+
builder.Services.AddOidcAuthentication(options =>
6+
{
7+
options.ProviderOptions.Authority = $"{builder.HostEnvironment.BaseAddress}oidc";
8+
options.ProviderOptions.ClientId = "s6BhdRkqt3";
9+
options.ProviderOptions.ResponseType = "code";
10+
});
11+
12+
await builder.Build().RunAsync();

0 commit comments

Comments
 (0)