diff --git a/Directory.Build.targets b/Directory.Build.targets index 91bb05f5..c95f8662 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -70,6 +70,8 @@ + $(DefineConstants);SUPPORTS_AUTHORIZATION_MIDDLEWARE + $(DefineConstants);SUPPORTS_ENDPOINT_ROUTING $(DefineConstants);SUPPORTS_HOST_APPLICATION_LIFETIME $(DefineConstants);SUPPORTS_HOST_ENVIRONMENT $(DefineConstants);SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION @@ -109,6 +111,7 @@ Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '7.0'))) "> $(DefineConstants);SUPPORTS_AUTHENTICATION_HANDLER_SELECTION_FALLBACK $(DefineConstants);SUPPORTS_BULK_DBSET_OPERATIONS + $(DefineConstants);SUPPORTS_REDIRECTION_ON_SIGN_IN + + + + + @@ -203,6 +208,7 @@ + diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs index 18c274be..8ec2e36a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs @@ -191,12 +191,19 @@ public class AuthenticationController : Controller OpenIddictClientAspNetCoreConstants.Tokens.BackchannelIdentityToken or OpenIddictClientAspNetCoreConstants.Tokens.RefreshToken)); +#if SUPPORTS_REDIRECTION_ON_SIGN_IN // Ask the default sign-in handler to return a new cookie and redirect the // user agent to the return URL stored in the authentication properties. // // For scenarios where the default sign-in handler configured in the ASP.NET Core // authentication options shouldn't be used, a specific scheme can be specified here. return SignIn(new ClaimsPrincipal(identity), properties); +#else + // Note: "return SignIn(...)" cannot be directly used as-is on ASP.NET Core <7.0, as the cookies handler + // doesn't allow redirecting from an endpoint that doesn't match the path set in the cookie options. + await HttpContext.SignInAsync(new ClaimsPrincipal(identity), properties); + return Redirect(properties.RedirectUri ?? "/"); +#endif } // Note: this controller uses the same callback action for all providers diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs index 40be2ae2..8abfeaee 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs @@ -49,7 +49,7 @@ public class HomeController : Controller return View("Index", new IndexViewModel { - Message = await response.Content.ReadAsStringAsync(cancellationToken), + Message = await response.Content.ReadAsStringAsync(), Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) where !string.IsNullOrEmpty(registration.ProviderName) where !string.IsNullOrEmpty(registration.ProviderDisplayName) diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj index e8b91d39..f97b3a4b 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj @@ -1,7 +1,7 @@  - net8.0 + net48;net8.0 false disable @@ -17,4 +17,14 @@ + + + + + + + diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Program.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Program.cs index d35cfa85..b5bbf62a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Program.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Program.cs @@ -1,11 +1,22 @@ -namespace OpenIddict.Sandbox.AspNetCore.Client; +using Microsoft.AspNetCore; + +namespace OpenIddict.Sandbox.AspNetCore.Client; public static class Program { +#if SUPPORTS_WEB_INTEGRATION_IN_GENERIC_HOST public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(builder => builder.UseStartup()); +#else + public static void Main(string[] args) => + CreateWebHostBuilder(args).Build().Run(); + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); +#endif } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs index fd27fe3e..e4dbff73 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs @@ -111,8 +111,9 @@ public class Startup RedirectUri = new Uri("callback/login/local", UriKind.Relative), PostLogoutRedirectUri = new Uri("callback/logout/local", UriKind.Relative), - // Instead of sending a client secret, this application authenticates by - // generating client assertions that are signed using a private signing key. +#if SUPPORTS_PEM_ENCODED_KEY_IMPORT + // On supported platforms, this application authenticates by generating JWT client + // assertions that are signed using a signing key instead of using a client secret. // // As such, no client secret is set, but an ECDSA key is registered and used by // the OpenIddict client to automatically generate client assertions when needed. @@ -129,6 +130,9 @@ public class Startup -----END EC PRIVATE KEY----- """), SecurityAlgorithms.EcdsaSha256, SecurityAlgorithms.Sha256) } +#else + ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654" +#endif }); // Register the Web providers integrations. @@ -160,6 +164,7 @@ public class Startup .SetDuration("permanent"); }); +#if SUPPORTS_PEM_ENCODED_KEY_IMPORT static ECDsaSecurityKey GetECDsaSigningKey(ReadOnlySpan key) { var algorithm = ECDsa.Create(); @@ -167,11 +172,12 @@ public class Startup return new ECDsaSecurityKey(algorithm); } +#endif }); services.AddHttpClient(); - services.AddControllersWithViews(); + services.AddMvc(); // Register the worker responsible for creating the database used to store tokens. // Note: in a real world application, this step should be part of a setup script. @@ -186,15 +192,23 @@ public class Startup app.UseStatusCodePagesWithReExecute("/error"); +#if SUPPORTS_ENDPOINT_ROUTING app.UseRouting(); - +#endif app.UseAuthentication(); + +#if SUPPORTS_AUTHORIZATION_MIDDLEWARE app.UseAuthorization(); +#endif +#if SUPPORTS_ENDPOINT_ROUTING app.UseEndpoints(options => { options.MapControllers(); options.MapDefaultControllerRoute(); }); +#else + app.UseMvcWithDefaultRoute(); +#endif } } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml index 979dfead..8601ccce 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml @@ -6,7 +6,7 @@ @model IndexViewModel
- @if (User?.Identity is { IsAuthenticated: true }) + @if (User.Identity != null && User.Identity.IsAuthenticated) {

Welcome, @User.Identity.Name

diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Worker.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Worker.cs index 98c9a9f2..c13e41bf 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Worker.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Worker.cs @@ -11,10 +11,26 @@ public class Worker : IHostedService public async Task StartAsync(CancellationToken cancellationToken) { - await using var scope = _serviceProvider.CreateAsyncScope(); + var scope = _serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - await context.Database.EnsureCreatedAsync(cancellationToken); + try + { + var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.EnsureCreatedAsync(cancellationToken); + } + + finally + { + if (scope is IAsyncDisposable disposable) + { + await disposable.DisposeAsync(); + } + + else + { + scope.Dispose(); + } + } } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs index 668fab03..3c054b3b 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs @@ -80,11 +80,18 @@ public class AuthenticationController : Controller OpenIddictClientAspNetCoreConstants.Tokens.BackchannelAccessToken or OpenIddictClientAspNetCoreConstants.Tokens.RefreshToken)); +#if SUPPORTS_REDIRECTION_ON_SIGN_IN // Ask the default sign-in handler to return a new cookie and redirect the // user agent to the return URL stored in the authentication properties. // // For scenarios where the default sign-in handler configured in the ASP.NET Core // authentication options shouldn't be used, a specific scheme can be specified here. return SignIn(new ClaimsPrincipal(identity), properties); +#else + // Note: "return SignIn(...)" cannot be directly used as-is on ASP.NET Core <7.0, as the cookies handler + // doesn't allow redirecting from an endpoint that doesn't match the path set in the cookie options. + await HttpContext.SignInAsync(new ClaimsPrincipal(identity), properties); + return Redirect(properties.RedirectUri ?? "/"); +#endif } } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs index 5244617e..6cfa8e55 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs @@ -96,7 +96,7 @@ public class AuthorizationController : Controller Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() : Request.Query.Where(parameter => parameter.Key != Parameters.Prompt).ToList(); - parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); + parameters.Add(new(Parameters.Prompt, new StringValues(prompt))); // For applications that want to allow the client to select the external authentication provider // that will be used to authenticate the user, the identity_provider parameter can be used for that. diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj index eefd3c7e..07d99588 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj @@ -1,7 +1,7 @@  - net8.0 + net48;net8.0 false false false @@ -21,4 +21,27 @@ + + + + + + + + + + + + reactive + + + + diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs index 2d5b2d60..ccb00df5 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs @@ -1,11 +1,22 @@ -namespace OpenIddict.Sandbox.AspNetCore.Server; +using Microsoft.AspNetCore; + +namespace OpenIddict.Sandbox.AspNetCore.Server; public static class Program { +#if SUPPORTS_WEB_INTEGRATION_IN_GENERIC_HOST public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(builder => builder.UseStartup()); +#else + public static void Main(string[] args) => + CreateWebHostBuilder(args).Build().Run(); + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); +#endif } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs index be27cb5c..ac8deb1a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs @@ -11,7 +11,7 @@ public class Startup { public void ConfigureServices(IServiceCollection services) { - services.AddControllersWithViews(); + services.AddMvc(); services.AddDbContext(options => { @@ -212,8 +212,9 @@ public class Startup app.UseStatusCodePagesWithReExecute("/error"); +#if SUPPORTS_ENDPOINT_ROUTING app.UseRouting(); - +#endif app.UseRequestLocalization(options => { options.AddSupportedCultures("en-US", "fr-FR"); @@ -222,12 +223,19 @@ public class Startup }); app.UseAuthentication(); + +#if SUPPORTS_AUTHORIZATION_MIDDLEWARE app.UseAuthorization(); +#endif +#if SUPPORTS_ENDPOINT_ROUTING app.UseEndpoints(options => { options.MapControllers(); options.MapDefaultControllerRoute(); }); +#else + app.UseMvcWithDefaultRoute(); +#endif } } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs index 83b6c9cf..1ba475f5 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs @@ -16,13 +16,29 @@ public class Worker : IHostedService public async Task StartAsync(CancellationToken cancellationToken) { - await using var scope = _serviceProvider.CreateAsyncScope(); + var scope = _serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - await context.Database.EnsureCreatedAsync(cancellationToken); + try + { + var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.EnsureCreatedAsync(cancellationToken); + + await RegisterApplicationsAsync(scope.ServiceProvider); + await RegisterScopesAsync(scope.ServiceProvider); + } + + finally + { + if (scope is IAsyncDisposable disposable) + { + await disposable.DisposeAsync(); + } - await RegisterApplicationsAsync(scope.ServiceProvider); - await RegisterScopesAsync(scope.ServiceProvider); + else + { + scope.Dispose(); + } + } static async Task RegisterApplicationsAsync(IServiceProvider provider) { @@ -76,6 +92,7 @@ public class Worker : IHostedService { ApplicationType = ApplicationTypes.Web, ClientId = "mvc", + ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", ClientType = ClientTypes.Confidential, ConsentType = ConsentTypes.Explicit, DisplayName = "MVC client application", @@ -83,12 +100,13 @@ public class Worker : IHostedService { [CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MVC" }, +#if SUPPORTS_PEM_ENCODED_KEY_IMPORT JsonWebKeySet = new JsonWebKeySet { Keys = { - // Instead of sending a client secret, this application authenticates by - // generating client assertions that are signed using an ECDSA signing key. + // On supported platforms, this application authenticates by generating JWT client + // assertions that are signed using a signing key instead of using a client secret. // // Note: while the client needs access to the private key, the server only needs // to know the public key to be able to validate the client assertions it receives. @@ -100,6 +118,7 @@ public class Worker : IHostedService """)) } }, +#endif RedirectUris = { new Uri("https://localhost:44381/callback/login/local") @@ -261,6 +280,7 @@ public class Worker : IHostedService }); } +#if SUPPORTS_PEM_ENCODED_KEY_IMPORT static ECDsaSecurityKey GetECDsaSigningKey(ReadOnlySpan key) { var algorithm = ECDsa.Create(); @@ -268,6 +288,7 @@ public class Worker : IHostedService return new ECDsaSecurityKey(algorithm); } +#endif } static async Task RegisterScopesAsync(IServiceProvider provider)