diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 3b121383..073a7aba 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1424,7 +1424,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
An error occurred while authenticating the user.
- The Windows protocol activation cannot be resolved from the client transaction or contained invalid data.
+ The Windows protocol activation cannot be resolved from the client transaction.
The identifier of the application instance that initiated the authentication process cannot be resolved from the state token.
@@ -1465,6 +1465,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
The payload extracted from the inter-process notification is malformed, incomplete or was created by a different version of the OpenIddict client library.
+
+ The OpenIddict client Windows integration is not supported on this platform.
+
The security token is missing.
diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsActivation.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsActivation.cs
index ccc5d426..920e0478 100644
--- a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsActivation.cs
+++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsActivation.cs
@@ -9,15 +9,35 @@ using System.ComponentModel;
namespace OpenIddict.Client.Windows;
///
-/// Represents a Windows application activation.
+/// Represents a Windows protocol activation.
///
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientWindowsActivation
{
///
- /// Gets or sets the activation URI used to activate the application.
+ /// Creates a new instance of the class.
///
- public Uri? ActivationUri { get; set; }
+ /// The protocol activation URI.
+ /// is .
+ public OpenIddictClientWindowsActivation(Uri uri)
+ {
+ if (uri is null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ if (!uri.IsAbsoluteUri || !uri.IsWellFormedOriginalString())
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(uri));
+ }
+
+ ActivationUri = uri;
+ }
+
+ ///
+ /// Gets the protocol activation URI.
+ ///
+ public Uri ActivationUri { get; }
///
/// Gets or sets a boolean indicating whether the activation
diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsExtensions.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsExtensions.cs
index 677b22e1..bcf60bfa 100644
--- a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsExtensions.cs
+++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsExtensions.cs
@@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
+using System.Runtime.InteropServices;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
@@ -30,19 +31,25 @@ public static class OpenIddictClientWindowsExtensions
throw new ArgumentNullException(nameof(builder));
}
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
+ }
+
// Note: the OpenIddict IHostedService implementation is deliberately registered as early as possible to
// ensure protocol activations can be handled before another service can stop the initialization of the
// application (e.g Dapplo.Microsoft.Extensions.Hosting.AppServices relies on an IHostedService to implement
// single instantiation, which would prevent the OpenIddict service from handling the protocol activation
// if the OpenIddict IHostedService implementation was not registered before the Dapplo IHostedService).
if (!builder.Services.Any(static descriptor => descriptor.ServiceType == typeof(IHostedService) &&
- descriptor.ImplementationType == typeof(OpenIddictClientWindowsService)))
+ descriptor.ImplementationType == typeof(OpenIddictClientWindowsHandler)))
{
- builder.Services.Insert(0, ServiceDescriptor.Singleton());
+ builder.Services.Insert(0, ServiceDescriptor.Singleton());
}
- // Register the marshal responsible for managing authentication operations.
+ // Register the services responsible for coordinating and managing authentication operations.
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
// Register the built-in filters used by the default OpenIddict Windows client event handlers.
builder.Services.TryAddSingleton();
diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsHandler.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsHandler.cs
new file mode 100644
index 00000000..427aad67
--- /dev/null
+++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsHandler.cs
@@ -0,0 +1,95 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+
+namespace OpenIddict.Client.Windows;
+
+///
+/// Contains the logic necessary to handle initial URI protocol activations.
+///
+///
+/// Note: redirected URI protocol activations are handled by .
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public sealed class OpenIddictClientWindowsHandler : IHostedService
+{
+ private readonly IOptionsMonitor _options;
+ private readonly OpenIddictClientWindowsService _service;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The OpenIddict client Windows integration options.
+ /// The OpenIddict client Windows service.
+ public OpenIddictClientWindowsHandler(
+ IOptionsMonitor options,
+ OpenIddictClientWindowsService service)
+ {
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ _service = service ?? throw new ArgumentNullException(nameof(service));
+ }
+
+ ///
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ // Note: initial URI protocol activation handling is implemented as a regular IHostedService
+ // rather than as a BackgroundService to allow blocking the initialization of the host until
+ // the activation is fully processed by the OpenIddict pipeline. By doing that, the UI thread
+ // is not started until redirection requests (like authorization responses) are fully processed,
+ // which allows handling these requests transparently and helps avoid the "flashing window effect":
+ // once a request has been handled by the OpenIddict pipeline, a dedicated handler is responsible
+ // for stopping the application gracefully using the IHostApplicationLifetime.StopApplication() API.
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ // If the default activation processing logic was disabled in the options, ignore the activation.
+ if (_options.CurrentValue.DisableProtocolActivationProcessing)
+ {
+ return Task.CompletedTask;
+ }
+
+ // Determine whether the current instance is initialized to react to a protocol activation.
+ // If it's not, return immediately to avoid adding latency to the application startup process.
+ if (GetProtocolActivation() is not OpenIddictClientWindowsActivation activation)
+ {
+ return Task.CompletedTask;
+ }
+
+ return _service.HandleProtocolActivationAsync(activation, cancellationToken);
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static OpenIddictClientWindowsActivation? GetProtocolActivation()
+ {
+#if SUPPORTS_WINDOWS_RUNTIME
+ // On platforms that support WinRT, always favor the AppInstance.GetActivatedEventArgs() API.
+ if (OpenIddictClientWindowsHelpers.IsWindowsRuntimeSupported() &&
+ OpenIddictClientWindowsHelpers.GetProtocolActivationUriWithWindowsRuntime() is Uri uri)
+ {
+ return new OpenIddictClientWindowsActivation(uri);
+ }
+#endif
+ // Otherwise, try to extract the protocol activation from the command line arguments.
+ if (OpenIddictClientWindowsHelpers.GetProtocolActivationUriFromCommandLineArguments(
+ Environment.GetCommandLineArgs()) is Uri value)
+ {
+ return new OpenIddictClientWindowsActivation(value);
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+}
diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsHandlers.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsHandlers.cs
index faa7c951..1b772467 100644
--- a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsHandlers.cs
+++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsHandlers.cs
@@ -90,8 +90,9 @@ public static partial class OpenIddictClientWindowsHandlers
(context.BaseUri, context.RequestUri) = context.Transaction.GetWindowsActivation() switch
{
- { ActivationUri: Uri uri } when uri.IsAbsoluteUri
- => (new Uri(uri.GetLeftPart(UriPartial.Authority), UriKind.Absolute), uri),
+ { ActivationUri: Uri uri } => (
+ BaseUri : new Uri(uri.GetLeftPart(UriPartial.Authority), UriKind.Absolute),
+ RequestUri: uri),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0375))
};
@@ -451,7 +452,7 @@ public static partial class OpenIddictClientWindowsHandlers
// Write the protocol activation URI.
writer.Write(context.Transaction.GetWindowsActivation() switch
{
- { ActivationUri: Uri uri } when uri.IsAbsoluteUri => uri.AbsoluteUri,
+ { ActivationUri: Uri uri } => uri.AbsoluteUri,
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0375))
});
diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs
index 20bc68fa..9495c7c3 100644
--- a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs
+++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs
@@ -6,16 +6,10 @@
using System.ComponentModel;
using System.IO.Pipes;
-using System.Runtime.CompilerServices;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-#if !SUPPORTS_HOST_APPLICATION_LIFETIME
-using IHostApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime;
-#endif
-
namespace OpenIddict.Client.Windows;
///
@@ -23,23 +17,29 @@ namespace OpenIddict.Client.Windows;
/// are redirected by other instances using inter-process communication.
///
///
-/// Note: initial URI protocol activations are handled by .
+/// Note: initial URI protocol activations are handled by .
///
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictClientWindowsListener : BackgroundService
{
private readonly ILogger _logger;
private readonly IOptionsMonitor _options;
- private readonly IServiceProvider _provider;
-
+ private readonly OpenIddictClientWindowsService _service;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The logger.
+ /// The OpenIddict client Windows integration options.
+ /// The OpenIddict client Windows service.
public OpenIddictClientWindowsListener(
ILogger logger,
IOptionsMonitor options,
- IServiceProvider provider)
+ OpenIddictClientWindowsService service)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
- _provider = provider ?? throw new ArgumentNullException(nameof(provider));
+ _service = service ?? throw new ArgumentNullException(nameof(service));
}
///
@@ -81,7 +81,7 @@ public sealed class OpenIddictClientWindowsListener : BackgroundService
await (reader.ReadInt32() switch
{
0x01 when GetProtocolActivation(reader) is OpenIddictClientWindowsActivation activation
- => HandleProtocolActivationAsync(_provider, activation, stoppingToken),
+ => _service.HandleProtocolActivationAsync(activation, stoppingToken),
var value => throw new InvalidOperationException(SR.FormatID0387(value))
});
@@ -101,7 +101,7 @@ public sealed class OpenIddictClientWindowsListener : BackgroundService
while (!stoppingToken.IsCancellationRequested);
- static OpenIddictClientWindowsActivation? GetProtocolActivation(BinaryReader reader)
+ static OpenIddictClientWindowsActivation GetProtocolActivation(BinaryReader reader)
{
// Ensure the binary serialization format is supported.
var version = reader.ReadInt32();
@@ -116,61 +116,10 @@ public sealed class OpenIddictClientWindowsListener : BackgroundService
throw new InvalidOperationException(SR.GetResourceString(SR.ID0388));
}
- return new OpenIddictClientWindowsActivation
+ return new OpenIddictClientWindowsActivation(uri)
{
- ActivationUri = uri,
IsActivationRedirected = true
};
}
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- static async Task HandleProtocolActivationAsync(IServiceProvider provider,
- OpenIddictClientWindowsActivation activation, CancellationToken cancellationToken)
- {
- var scope = provider.CreateScope();
-
- try
- {
- var dispatcher = scope.ServiceProvider.GetRequiredService();
- var factory = scope.ServiceProvider.GetRequiredService();
-
- // Create a client transaction and store the protocol activation details so they can be
- // retrieved by the Windows-specific client event handlers that need to access them.
- var transaction = await factory.CreateTransactionAsync();
- transaction.SetProperty(typeof(OpenIddictClientWindowsActivation).FullName!, activation);
-
- var context = new ProcessRequestContext(transaction)
- {
- CancellationToken = cancellationToken
- };
-
- await dispatcher.DispatchAsync(context);
-
- if (context.IsRejected)
- {
- await dispatcher.DispatchAsync(new ProcessErrorContext(transaction)
- {
- CancellationToken = cancellationToken,
- Error = context.Error ?? Errors.InvalidRequest,
- ErrorDescription = context.ErrorDescription,
- ErrorUri = context.ErrorUri,
- Response = new OpenIddictResponse()
- });
- }
- }
-
- finally
- {
- if (scope is IAsyncDisposable disposable)
- {
- await disposable.DisposeAsync();
- }
-
- else
- {
- scope.Dispose();
- }
- }
- }
}
}
diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs
index a605d5fb..9be0b337 100644
--- a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs
+++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs
@@ -5,150 +5,88 @@
*/
using System.ComponentModel;
-using System.Runtime.CompilerServices;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Options;
namespace OpenIddict.Client.Windows;
///
-/// Contains the logic necessary to handle initial URI protocol activations.
+/// Contains the logic necessary to handle URI protocol activations (that
+/// are typically resolved when launching the application or redirected
+/// by other instances using inter-process communication).
///
-///
-/// Note: redirected URI protocol activations are handled by .
-///
-[EditorBrowsable(EditorBrowsableState.Never)]
-public sealed class OpenIddictClientWindowsService : IHostedService
+public sealed class OpenIddictClientWindowsService
{
- private readonly IOptionsMonitor _options;
private readonly IServiceProvider _provider;
///
/// Creates a new instance of the class.
///
- /// The OpenIddict client Windows integration options.
/// The service provider.
/// is .
- public OpenIddictClientWindowsService(
- IOptionsMonitor options,
- IServiceProvider provider)
- {
- _options = options ?? throw new ArgumentNullException(nameof(options));
- _provider = provider ?? throw new ArgumentNullException(nameof(provider));
- }
+ public OpenIddictClientWindowsService(IServiceProvider provider)
+ => _provider = provider ?? throw new ArgumentNullException(nameof(provider));
- ///
- public Task StartAsync(CancellationToken cancellationToken)
+ ///
+ /// Handles the specified protocol activation.
+ ///
+ /// The protocol activation details.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ /// is .
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public async Task HandleProtocolActivationAsync(
+ OpenIddictClientWindowsActivation activation, CancellationToken cancellationToken = default)
{
- // Note: initial URI protocol activation handling is implemented as a regular IHostedService
- // rather than as a BackgroundService to allow blocking the initialization of the host until
- // the activation is fully processed by the OpenIddict pipeline. By doing that, the UI thread
- // is not started until redirection requests (like authorization responses) are fully processed,
- // which allows handling these requests transparently and helps avoid the "flashing window effect":
- // once a request has been handled by the OpenIddict pipeline, a dedicated handler is responsible
- // for stopping the application gracefully using the IHostApplicationLifetime.StopApplication() API.
-
- if (cancellationToken.IsCancellationRequested)
+ if (activation is null)
{
- return Task.FromCanceled(cancellationToken);
+ throw new ArgumentNullException(nameof(activation));
}
- // If the default activation processing logic was disabled in the options, ignore the activation.
- if (_options.CurrentValue.DisableProtocolActivationProcessing)
- {
- return Task.CompletedTask;
- }
+ cancellationToken.ThrowIfCancellationRequested();
- // Determine whether the current instance is initialized to react to a protocol activation.
- // If it's not, return immediately to avoid adding latency to the application startup process.
- if (GetProtocolActivation() is not OpenIddictClientWindowsActivation activation)
+ var scope = _provider.CreateScope();
+
+ try
{
- return Task.CompletedTask;
- }
+ var dispatcher = scope.ServiceProvider.GetRequiredService();
+ var factory = scope.ServiceProvider.GetRequiredService();
- return HandleProtocolActivationAsync(_provider, activation, cancellationToken);
+ // Create a client transaction and store the protocol activation details so they can be
+ // retrieved by the Windows-specific client event handlers that need to access them.
+ var transaction = await factory.CreateTransactionAsync();
+ transaction.SetProperty(typeof(OpenIddictClientWindowsActivation).FullName!, activation);
- [MethodImpl(MethodImplOptions.NoInlining)]
- static OpenIddictClientWindowsActivation? GetProtocolActivation()
- {
-#if SUPPORTS_WINDOWS_RUNTIME
- // On platforms that support WinRT, always favor the AppInstance.GetActivatedEventArgs() API.
- if (OpenIddictClientWindowsHelpers.IsWindowsRuntimeSupported() &&
- OpenIddictClientWindowsHelpers.GetProtocolActivationUriWithWindowsRuntime() is Uri uri)
+ var context = new ProcessRequestContext(transaction)
{
- return new OpenIddictClientWindowsActivation
- {
- ActivationUri = uri,
- IsActivationRedirected = false
- };
- }
-#endif
- // Otherwise, try to extract the protocol activation from the command line arguments.
- if (OpenIddictClientWindowsHelpers.GetProtocolActivationUriFromCommandLineArguments(
- Environment.GetCommandLineArgs()) is Uri value)
+ CancellationToken = cancellationToken
+ };
+
+ await dispatcher.DispatchAsync(context);
+
+ if (context.IsRejected)
{
- return new OpenIddictClientWindowsActivation
+ await dispatcher.DispatchAsync(new ProcessErrorContext(transaction)
{
- ActivationUri = value,
- IsActivationRedirected = false
- };
+ CancellationToken = cancellationToken,
+ Error = context.Error ?? Errors.InvalidRequest,
+ ErrorDescription = context.ErrorDescription,
+ ErrorUri = context.ErrorUri,
+ Response = new OpenIddictResponse()
+ });
}
-
- return null;
}
- [MethodImpl(MethodImplOptions.NoInlining)]
- static async Task HandleProtocolActivationAsync(IServiceProvider provider,
- OpenIddictClientWindowsActivation activation, CancellationToken cancellationToken)
+ finally
{
- var scope = provider.CreateScope();
-
- try
+ if (scope is IAsyncDisposable disposable)
{
- var dispatcher = scope.ServiceProvider.GetRequiredService();
- var factory = scope.ServiceProvider.GetRequiredService();
-
- // Create a client transaction and store the protocol activation details so they can be
- // retrieved by the Windows-specific client event handlers that need to access them.
- var transaction = await factory.CreateTransactionAsync();
- transaction.SetProperty(typeof(OpenIddictClientWindowsActivation).FullName!, activation);
-
- var context = new ProcessRequestContext(transaction)
- {
- CancellationToken = cancellationToken
- };
-
- await dispatcher.DispatchAsync(context);
-
- if (context.IsRejected)
- {
- await dispatcher.DispatchAsync(new ProcessErrorContext(transaction)
- {
- CancellationToken = cancellationToken,
- Error = context.Error ?? Errors.InvalidRequest,
- ErrorDescription = context.ErrorDescription,
- ErrorUri = context.ErrorUri,
- Response = new OpenIddictResponse()
- });
- }
+ await disposable.DisposeAsync();
}
- finally
+ else
{
- if (scope is IAsyncDisposable disposable)
- {
- await disposable.DisposeAsync();
- }
-
- else
- {
- scope.Dispose();
- }
+ scope.Dispose();
}
}
}
-
- ///
- public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}