diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1f5abebd..9d87f76e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -194,7 +194,8 @@
-
+
+
diff --git a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs
index 0060595d..c96062e0 100644
--- a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs
+++ b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs
@@ -4,6 +4,10 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Abstractions.OpenIddictExceptions;
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
+#if !SUPPORTS_HOST_APPLICATION_LIFETIME
+using IHostApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime;
+#endif
+
namespace OpenIddict.Sandbox.Console.Client;
using Console = System.Console;
@@ -38,7 +42,7 @@ public class InteractiveService : BackgroundService
{
Console.WriteLine("Type '1' + ENTER to log in using the local server or '2' + ENTER to log in using GitHub.");
- provider = await WaitAsync(Task.Run(Console.ReadLine, stoppingToken), stoppingToken) switch
+ provider = await ReadLineAsync(stoppingToken) switch
{
"1" => "Local",
"2" => Providers.GitHub,
@@ -76,8 +80,12 @@ public class InteractiveService : BackgroundService
}
}
- static async Task WaitAsync(Task task, CancellationToken cancellationToken)
+ static async Task ReadLineAsync(CancellationToken cancellationToken)
{
+#if SUPPORTS_TASK_WAIT_ASYNC
+ return await Task.Run(Console.ReadLine, cancellationToken).WaitAsync(cancellationToken);
+#else
+ var task = Task.Run(Console.ReadLine, cancellationToken);
var source = new TaskCompletionSource(TaskCreationOptions.None);
using (cancellationToken.Register(static state => ((TaskCompletionSource) state!).SetResult(true), source))
@@ -89,6 +97,7 @@ public class InteractiveService : BackgroundService
return await task;
}
+#endif
}
}
}
diff --git a/sandbox/OpenIddict.Sandbox.Console.Client/OpenIddict.Sandbox.Console.Client.csproj b/sandbox/OpenIddict.Sandbox.Console.Client/OpenIddict.Sandbox.Console.Client.csproj
index 59f62c25..2a61ab12 100644
--- a/sandbox/OpenIddict.Sandbox.Console.Client/OpenIddict.Sandbox.Console.Client.csproj
+++ b/sandbox/OpenIddict.Sandbox.Console.Client/OpenIddict.Sandbox.Console.Client.csproj
@@ -16,9 +16,12 @@
-
+
+
+
+
diff --git a/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs b/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs
index 3a56a91d..b340121c 100644
--- a/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs
+++ b/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs
@@ -52,7 +52,8 @@ var host = new HostBuilder()
.DisableActivationRedirection()
.DisablePipeServer()
.EnableEmbeddedWebServer()
- .UseSystemBrowser();
+ .UseSystemBrowser()
+ .SetApplicationDiscriminator("0XP3WQ07VVMCVBJ");
// Register the System.Net.Http integration and use the identity of the current
// assembly as a more specific user agent, which can be useful when dealing with
@@ -92,6 +93,9 @@ var host = new HostBuilder()
// Register the background service responsible for handling the console interactions.
services.AddHostedService();
+
+ // Prevent the console lifetime manager from writing status messages to the output stream.
+ services.Configure(options => options.SuppressStatusMessages = true);
})
.UseConsoleLifetime()
.Build();
diff --git a/sandbox/OpenIddict.Sandbox.WinForms.Client/OpenIddict.Sandbox.WinForms.Client.csproj b/sandbox/OpenIddict.Sandbox.WinForms.Client/OpenIddict.Sandbox.WinForms.Client.csproj
index 516d4e5d..2607d21f 100644
--- a/sandbox/OpenIddict.Sandbox.WinForms.Client/OpenIddict.Sandbox.WinForms.Client.csproj
+++ b/sandbox/OpenIddict.Sandbox.WinForms.Client/OpenIddict.Sandbox.WinForms.Client.csproj
@@ -21,7 +21,7 @@
-
+
diff --git a/sandbox/OpenIddict.Sandbox.Wpf.Client/OpenIddict.Sandbox.Wpf.Client.csproj b/sandbox/OpenIddict.Sandbox.Wpf.Client/OpenIddict.Sandbox.Wpf.Client.csproj
index 67baa904..0fe35d1c 100644
--- a/sandbox/OpenIddict.Sandbox.Wpf.Client/OpenIddict.Sandbox.Wpf.Client.csproj
+++ b/sandbox/OpenIddict.Sandbox.Wpf.Client/OpenIddict.Sandbox.Wpf.Client.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 9ead9617..1e91effb 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1457,10 +1457,10 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
The default system browser couldn't be started. If the application executes inside a sandbox, make sure it is allowed to launch URIs or spawn new processes.
- A pipe name must be manually set in the OpenIddict client system integration options when no application name was configured in the .NET generic host options. To set the pipe name, call 'services.AddOpenIddict().AddClient().UseOperatingSystemIntegration().SetPipeName()'.
+ An application discriminator must be manually set in the OpenIddict client system integration options when no application name is provided by the .NET generic host. To set the application discriminator, call 'services.AddOpenIddict().AddClient().UseSystemIntegration().SetApplicationDiscriminator()'.
- The type extracted from the inter-process notification ({0}) is unknown or not valid, which may indicate that different versions of the OpenIddict client are used for the same application.
+ The type extracted from the inter-process notification ({0}) is unknown or invalid, which may indicate that different versions of the OpenIddict client are used for the same application.
The payload extracted from the inter-process notification is malformed, incomplete or was created by a different version of the OpenIddict client library.
diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs
index be183899..ffce9bb7 100644
--- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs
+++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs
@@ -175,6 +175,23 @@ public sealed class OpenIddictClientSystemIntegrationBuilder
public OpenIddictClientSystemIntegrationBuilder EnablePipeServer()
=> Configure(options => options.EnablePipeServer = true);
+ ///
+ /// Sets the application discriminator used to define a static pipe
+ /// name that will be shared by all the instances of the application.
+ ///
+ /// The application discriminator.
+ /// The .
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public OpenIddictClientSystemIntegrationBuilder SetApplicationDiscriminator(string discriminator)
+ {
+ if (string.IsNullOrEmpty(discriminator))
+ {
+ throw new ArgumentException(SR.FormatID0366(nameof(discriminator)), nameof(discriminator));
+ }
+
+ return Configure(options => options.ApplicationDiscriminator = discriminator);
+ }
+
///
/// Sets the identifier used to represent the current application
/// instance and redirect protocol activations when necessary.
diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs
index 82cc11fc..2aaaf2a3 100644
--- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs
+++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs
@@ -63,13 +63,6 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
// If no explicit client URI was set, default to the static "http://localhost/" address, which is
// adequate for a native/mobile client and points to the embedded web server when it is enabled.
- //
- // Note: while the RFC8252 specification recommends using 127.0.0.1 or ::1 instead of "localhost",
- // OpenIddict deliberately uses "localhost" to be compatible with platforms that don't allow binding
- // on loopback addresses without administrator rights and to avoid using a hard-to-predict client URI
- // that would be tied to a specific IP version dependent on the protocols supported/allowed by the OS.
- //
- // See https://www.rfc-editor.org/rfc/rfc8252#section-8.3 for more information.
options.ClientUri ??= new Uri("http://localhost/", UriKind.Absolute);
}
@@ -96,21 +89,29 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
options.EnablePipeServer ??= true;
options.EnableEmbeddedWebServer ??= HttpListener.IsSupported;
+ // If no explicit application discriminator was specified, compute the SHA-256 hash
+ // of the application name resolved from the host and use it as a unique identifier.
+ if (string.IsNullOrEmpty(options.ApplicationDiscriminator))
+ {
+ if (string.IsNullOrEmpty(_environment.ApplicationName))
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0386));
+ }
+
+ options.ApplicationDiscriminator = Base64UrlEncoder.Encode(
+ OpenIddictHelpers.ComputeSha256Hash(
+ Encoding.UTF8.GetBytes(_environment.ApplicationName)));
+ }
+
// If no explicit instance identifier was specified, use a random GUID.
if (string.IsNullOrEmpty(options.InstanceIdentifier))
{
options.InstanceIdentifier = Guid.NewGuid().ToString();
}
- // If no explicit pipe name was specified, compute the SHA-256 hash of the
- // application name resolved from the host and use it as a unique identifier.
+ // If no explicit pipe name was specified, build one using the application discriminator.
if (string.IsNullOrEmpty(options.PipeName))
{
- if (string.IsNullOrEmpty(_environment.ApplicationName))
- {
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0386));
- }
-
var builder = new StringBuilder();
// Note: on Windows, the name is deliberately prefixed with "LOCAL\" to support
@@ -121,11 +122,10 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
builder.Append(@"LOCAL\");
}
- builder.Append(@"OpenIddict.Client.SystemIntegration-");
- builder.Append(Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash(
- Encoding.UTF8.GetBytes(_environment.ApplicationName))));
-
- options.PipeName = builder.ToString();
+ options.PipeName = builder.Append("OpenIddict.Client.SystemIntegration")
+ .Append('-')
+ .Append(options.ApplicationDiscriminator)
+ .ToString();
}
#if SUPPORTS_CURRENT_USER_ONLY_PIPE_OPTION
diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationOptions.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationOptions.cs
index 22b7094a..fdd775d5 100644
--- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationOptions.cs
+++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationOptions.cs
@@ -77,6 +77,12 @@ public sealed class OpenIddictClientSystemIntegrationOptions
///
public bool? EnablePipeServer { get; set; }
+ ///
+ /// Gets or sets the application discriminator used to define a static
+ /// pipe name that will be shared by all the instances of the application.
+ ///
+ public string? ApplicationDiscriminator { get; set; }
+
///
/// Gets or sets the identifier used to represent the current application
/// instance and redirect protocol activations when necessary.
diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs
index c015fe45..ec1253f2 100644
--- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs
+++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs
@@ -49,16 +49,9 @@ public sealed class OpenIddictClientSystemIntegrationService
/// A that can be used to monitor the asynchronous operation.
/// is .
[EditorBrowsable(EditorBrowsableState.Advanced)]
- public Task HandleProtocolActivationAsync(OpenIddictClientSystemIntegrationActivation activation,
- CancellationToken cancellationToken = default)
- {
- if (activation is null)
- {
- throw new ArgumentNullException(nameof(activation));
- }
-
- return HandleRequestAsync(activation, cancellationToken);
- }
+ public Task HandleProtocolActivationAsync(
+ OpenIddictClientSystemIntegrationActivation activation, CancellationToken cancellationToken = default)
+ => HandleRequestAsync(activation ?? throw new ArgumentNullException(nameof(activation)), cancellationToken);
///
/// Handles the specified HTTP request.
@@ -68,14 +61,7 @@ public sealed class OpenIddictClientSystemIntegrationService
/// A that can be used to monitor the asynchronous operation.
/// is .
internal Task HandleHttpRequestAsync(HttpListenerContext request, CancellationToken cancellationToken = default)
- {
- if (request is null)
- {
- throw new ArgumentNullException(nameof(request));
- }
-
- return HandleRequestAsync(request, cancellationToken);
- }
+ => HandleRequestAsync(request ?? throw new ArgumentNullException(nameof(request)), cancellationToken);
#if SUPPORTS_WINDOWS_RUNTIME
///
@@ -86,14 +72,7 @@ public sealed class OpenIddictClientSystemIntegrationService
/// A that can be used to monitor the asynchronous operation.
/// is .
internal Task HandleWebAuthenticationResultAsync(WebAuthenticationResult result, CancellationToken cancellationToken = default)
- {
- if (result is null)
- {
- throw new ArgumentNullException(nameof(result));
- }
-
- return HandleRequestAsync(result, cancellationToken);
- }
+ => HandleRequestAsync(result ?? throw new ArgumentNullException(nameof(result)), cancellationToken);
#endif
///