Browse Source

Introduce Android support

pull/2136/head
Kévin Chalet 2 years ago
parent
commit
25b16b082c
  1. 22
      Directory.Build.props
  2. 66
      Directory.Build.targets
  3. 1
      Directory.Packages.props
  4. 5
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  5. 19
      src/OpenIddict.Client.SystemIntegration/OpenIddict.Client.SystemIntegration.csproj
  6. 6
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationActivationHandler.cs
  7. 8
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationAuthenticationMode.cs
  8. 29
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs
  9. 56
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs
  10. 33
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs
  11. 77
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlerFilters.cs
  12. 132
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs
  13. 131
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs
  14. 152
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs
  15. 85
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs
  16. 2
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs
  17. 18
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs
  18. 1
      src/OpenIddict/OpenIddict.csproj

22
Directory.Build.props

@ -34,6 +34,18 @@
</PropertyGroup>
<PropertyGroup>
<!--
Note: targeting Android requires installing the .NET Android workload. To ensure the solution can
be built on machines that don't have the Android workload installed, a directory check is used to
ensure the Android reference assemblies pack is present on the machine before targeting Android.
-->
<SupportsAndroidTargeting
Condition=" '$(SupportsAndroidTargeting)' == '' And
($([System.OperatingSystem]::IsMacOS()) Or $([System.OperatingSystem]::IsWindows())) And
('$(GITHUB_ACTIONS)' == 'true' Or ('$(DotNetRoot)' != '' And Exists('$(DotNetRoot)packs\Microsoft.Android.Ref.34')) Or
('$(DOTNET_HOST_PATH)' != '' And Exists('$([System.IO.Path]::GetDirectoryName($(DOTNET_HOST_PATH)))\packs\Microsoft.Android.Ref.34')) Or
('$(MSBuildRuntimeType)' != 'Core' And Exists('$(ProgramFiles)\dotnet\packs\Microsoft.Android.Ref.34'))) ">true</SupportsAndroidTargeting>
<!--
Note: targeting iOS requires installing the .NET iOS workload. To ensure the solution can be
built on machines that don't have the iOS workload installed, a directory check is used to
@ -69,8 +81,9 @@
('$(MSBuildRuntimeType)' != 'Core' And Exists('$(ProgramFiles)\dotnet\packs\Microsoft.macOS.Ref'))) ">true</SupportsMacOSTargeting>
<!--
Note: while <EnableWindowsTargeting>true</EnableWindowsTargeting> can be used to allow targeting Windows,
Windows targets are only used when running on Windows to speed up the build on non-Windows platforms.
Note: while <EnableWindowsTargeting>true</EnableWindowsTargeting> can be used to force targeting
Windows on non-Windows platforms, Windows-specific targets are only used when running on Windows
to speed up the build on non-Windows platforms.
-->
<SupportsWindowsTargeting
Condition=" '$(SupportsWindowsTargeting)' == '' And $([System.OperatingSystem]::IsWindows()) ">true</SupportsWindowsTargeting>
@ -98,6 +111,11 @@
net8.0
</NetCoreTargetFrameworks>
<NetCoreAndroidTargetFrameworks
Condition=" '$(NetCoreAndroidTargetFrameworks)' == '' And '$(SupportsAndroidTargeting)' == 'true' ">
net8.0-android34.0
</NetCoreAndroidTargetFrameworks>
<NetCoreIOSTargetFrameworks
Condition=" '$(NetCoreIOSTargetFrameworks)' == '' And '$(SupportsIOSTargeting)' == 'true' ">
net8.0-ios12.0;

66
Directory.Build.targets

@ -15,39 +15,31 @@
<PublicSign>false</PublicSign>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'iOS' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '12.0'))) ">
<SupportedOSPlatformVersion>12.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'MacCatalyst' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '13.1'))) ">
<SupportedOSPlatformVersion>13.1</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'macOS' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '10.15'))) ">
<SupportedOSPlatformVersion>10.15</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'Windows' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionEquals($(TargetPlatformVersion), '7.0'))) ">
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'Windows' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '10.0.17763'))) ">
<SupportedOSPlatformVersion>10.0.17763</SupportedOSPlatformVersion>
<PropertyGroup>
<SupportedOSPlatformVersion
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'Android' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '21.0'))) ">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'iOS' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '12.0'))) ">12.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'MacCatalyst' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '13.1'))) ">13.1</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'macOS' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '10.15'))) ">10.15</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'Windows' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '7.0'))) ">7.0</SupportedOSPlatformVersion>
</PropertyGroup>
<!--
@ -157,6 +149,14 @@
<DefineConstants>$(DefineConstants);SUPPORTS_TIME_PROVIDER</DefineConstants>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'Android' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '21.0'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_ANDROID</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ANDROIDX_BROWSER</DefineConstants>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'iOS' And '$(TargetPlatformVersion)' != '' And

1
Directory.Packages.props

@ -343,6 +343,7 @@
<PackageVersion Include="MongoDB.Bson" Version="2.20.0" />
<PackageVersion Include="MongoDB.Driver" Version="2.20.0" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.5.0" />
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.8.0.3" />
<!--
Note: the following references are exclusively used in the test projects:

5
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1468,7 +1468,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<value>The payload extracted from the inter-process notification is malformed, incomplete or was created by a different version of the OpenIddict client library.</value>
</data>
<data name="ID0389" xml:space="preserve">
<value>The OpenIddict client system integration is not supported on this platform.</value>
<value>The OpenIddict client system integration is not supported on this platform (only Android 21+, iOS 12.0+, Linux, Mac Catalyst 13.1+, macOS 10.15+ and Windows 7 are supported).</value>
</data>
<data name="ID0390" xml:space="preserve">
<value>The HTTP listener context cannot be resolved or contains invalid data.</value>
@ -1689,6 +1689,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<data name="ID0451" xml:space="preserve">
<value>The Zoho integration requires sending the region of the server when using the client credentials or refresh token grants. For that, attach a ".location" authentication property containing the region to use.</value>
</data>
<data name="ID0452" xml:space="preserve">
<value>Custom tabs intents are only supported on Android.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

19
src/OpenIddict.Client.SystemIntegration/OpenIddict.Client.SystemIntegration.csproj

@ -4,6 +4,7 @@
<TargetFrameworks>
$(NetFrameworkTargetFrameworks);
$(NetCoreTargetFrameworks);
$(NetCoreAndroidTargetFrameworks);
$(NetCoreIOSTargetFrameworks);
$(NetCoreMacCatalystTargetFrameworks);
$(NetCoreMacOSTargetFrameworks);
@ -12,11 +13,18 @@
$(UniversalWindowsPlatformTargetFrameworks)
</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!--
Note: the Xamarin.AndroidX.Browser package is not strong-named. Since it's only referenced
by Android-specific TFMs that cannot be used with the .NET Framework runtime (that doesn't
allow loading dependencies that are not strong-named), the warning can be safely disabled.
-->
<NoWarn>$(NoWarn);CS8002</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Description>Operating system integration package for the OpenIddict client.</Description>
<PackageTags>$(PackageTags);client;ios;linux;maccatalyst;macos;windows</PackageTags>
<PackageTags>$(PackageTags);client;android;ios;linux;maccatalyst;macos;windows</PackageTags>
</PropertyGroup>
<ItemGroup>
@ -41,6 +49,13 @@
<PackageReference Include="Microsoft.Windows.SDK.Contracts" />
</ItemGroup>
<ItemGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0')) And
'$(TargetPlatformIdentifier)' == 'Android' And '$(TargetPlatformVersion)' != '' And
$([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '21.0'))) ">
<PackageReference Include="Xamarin.AndroidX.Browser" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
@ -50,10 +65,12 @@
<Using Include="OpenIddict.Client.OpenIddictClientHandlerFilters" Static="true" />
<Using Include="OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrationHandlers" Static="true" />
<Using Include="OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrationHandlerFilters" Static="true" />
<Using Include="OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrationHelpers" Static="true" />
</ItemGroup>
<ItemGroup>
<SupportedPlatform Remove="@(SupportedPlatform)" />
<SupportedPlatform Include="android" />
<SupportedPlatform Include="ios" />
<SupportedPlatform Include="linux" />
<SupportedPlatform Include="maccatalyst" />

6
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationActivationHandler.cs

@ -72,15 +72,13 @@ public sealed class OpenIddictClientSystemIntegrationActivationHandler : IHosted
{
#if SUPPORTS_WINDOWS_RUNTIME
// On platforms that support WinRT, always favor the AppInstance.GetActivatedEventArgs() API.
if (OpenIddictClientSystemIntegrationHelpers.IsAppInstanceActivationSupported() &&
OpenIddictClientSystemIntegrationHelpers.GetProtocolActivationUriWithWindowsRuntime() is Uri uri)
if (IsAppInstanceActivationSupported() && GetProtocolActivationUriWithWindowsRuntime() is Uri uri)
{
return new OpenIddictClientSystemIntegrationActivation(uri);
}
#endif
// Otherwise, try to extract the protocol activation from the command line arguments.
if (OpenIddictClientSystemIntegrationHelpers.GetProtocolActivationUriFromCommandLineArguments(
Environment.GetCommandLineArgs()) is Uri value)
if (GetProtocolActivationUriFromCommandLineArguments(Environment.GetCommandLineArgs()) is Uri value)
{
return new OpenIddictClientSystemIntegrationActivation(value);
}

8
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationAuthenticationMode.cs

@ -34,5 +34,11 @@ public enum OpenIddictClientSystemIntegrationAuthenticationMode
[SupportedOSPlatform("ios12.0")]
[SupportedOSPlatform("maccatalyst13.1")]
[SupportedOSPlatform("macos10.15")]
ASWebAuthenticationSession = 2
ASWebAuthenticationSession = 2,
/// <summary>
/// Custom tabs intent-based authentication and logout.
/// </summary>
[SupportedOSPlatform("android21.0")]
CustomTabsIntent = 3
}

29
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationBuilder.cs

@ -9,6 +9,7 @@ using System.IO.Pipes;
using System.Net;
using System.Runtime.Versioning;
using OpenIddict.Client.SystemIntegration;
using static OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrationAuthenticationMode;
namespace Microsoft.Extensions.DependencyInjection;
@ -58,13 +59,27 @@ public sealed class OpenIddictClientSystemIntegrationBuilder
[SupportedOSPlatform("macos10.15")]
public OpenIddictClientSystemIntegrationBuilder UseASWebAuthenticationSession()
{
if (!OpenIddictClientSystemIntegrationHelpers.IsASWebAuthenticationSessionSupported())
if (!IsASWebAuthenticationSessionSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
}
return Configure(options => options.AuthenticationMode =
OpenIddictClientSystemIntegrationAuthenticationMode.ASWebAuthenticationSession);
return Configure(options => options.AuthenticationMode = ASWebAuthenticationSession);
}
/// <summary>
/// Uses a custom tabs intent to start interactive authentication and logout flows.
/// </summary>
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
[SupportedOSPlatform("android21.0")]
public OpenIddictClientSystemIntegrationBuilder UseCustomTabsIntent()
{
if (!IsCustomTabsIntentSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
}
return Configure(options => options.AuthenticationMode = CustomTabsIntent);
}
/// <summary>
@ -78,13 +93,12 @@ public sealed class OpenIddictClientSystemIntegrationBuilder
[SupportedOSPlatform("windows10.0.17763")]
public OpenIddictClientSystemIntegrationBuilder UseWebAuthenticationBroker()
{
if (!OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported())
if (!IsWebAuthenticationBrokerSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392));
}
return Configure(options => options.AuthenticationMode =
OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker);
return Configure(options => options.AuthenticationMode = WebAuthenticationBroker);
}
/// <summary>
@ -92,8 +106,7 @@ public sealed class OpenIddictClientSystemIntegrationBuilder
/// </summary>
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
public OpenIddictClientSystemIntegrationBuilder UseSystemBrowser()
=> Configure(options => options.AuthenticationMode =
OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser);
=> Configure(options => options.AuthenticationMode = SystemBrowser);
/// <summary>
/// Sets the list of static ports the embedded web server will be allowed to

56
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs

@ -15,6 +15,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
using static OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrationAuthenticationMode;
#if !SUPPORTS_HOST_ENVIRONMENT
using IHostEnvironment = Microsoft.Extensions.Hosting.IHostingEnvironment;
@ -72,7 +73,9 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
throw new ArgumentNullException(nameof(options));
}
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) &&
// Ensure the operating system is supported.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("android")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Create("maccatalyst")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
@ -81,8 +84,15 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
}
#if !SUPPORTS_ANDROID
// When running on Android, iOS, Mac Catalyst or macOS, ensure the version compiled for
// these platforms is used to prevent the generic/non-OS specific TFM from being used.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("android")))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#if !SUPPORTS_APPKIT
// When running on iOS, Mac Catalyst or macOS, ensure the version compiled for these platforms is used.
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
@ -95,26 +105,49 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#if SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON
// Ensure the operating system version is supported.
if ((OperatingSystem.IsAndroid() && !OperatingSystem.IsAndroidVersionAtLeast(21)) ||
(OperatingSystem.IsIOS() && !OperatingSystem.IsIOSVersionAtLeast(12)) ||
(OperatingSystem.IsMacCatalyst() && !OperatingSystem.IsMacCatalystVersionAtLeast(13, 1)) ||
(OperatingSystem.IsMacOS() && !OperatingSystem.IsMacOSVersionAtLeast(10, 15)) ||
(OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(7)))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
}
#else
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !IsWindowsVersionAtLeast(7))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
}
#endif
#pragma warning disable CA1416
// If explicitly set, ensure the specified authentication mode is supported.
if (options.AuthenticationMode is OpenIddictClientSystemIntegrationAuthenticationMode.ASWebAuthenticationSession &&
!OpenIddictClientSystemIntegrationHelpers.IsASWebAuthenticationSessionSupported())
if (options.AuthenticationMode is ASWebAuthenticationSession && !IsASWebAuthenticationSessionSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
}
else if (options.AuthenticationMode is OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker &&
!OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported())
else if (options.AuthenticationMode is CustomTabsIntent && !IsCustomTabsIntentSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
}
else if (options.AuthenticationMode is WebAuthenticationBroker && !IsWebAuthenticationBrokerSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392));
}
#pragma warning restore CA1416
options.AuthenticationMode ??= OpenIddictClientSystemIntegrationHelpers.IsASWebAuthenticationSessionSupported() ?
OpenIddictClientSystemIntegrationAuthenticationMode.ASWebAuthenticationSession :
OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser;
// When possible, always prefer OS-managed modes. Otherwise, fall back to the system browser.
options.AuthenticationMode ??=
IsASWebAuthenticationSessionSupported() ? ASWebAuthenticationSession :
IsCustomTabsIntentSupported() ? CustomTabsIntent : SystemBrowser;
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) &&
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("android")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Create("maccatalyst")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
@ -190,8 +223,7 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
{
using var identity = WindowsIdentity.GetCurrent(TokenAccessLevels.Query);
if (!OpenIddictClientSystemIntegrationHelpers.IsWindowsVersionAtLeast(10, 0, 10240) ||
!OpenIddictClientSystemIntegrationHelpers.HasAppContainerToken(identity))
if (!IsWindowsVersionAtLeast(10, 0, 10240) || !HasAppContainerToken(identity))
{
options.PipeSecurity = new PipeSecurity();
options.PipeSecurity.SetOwner(identity.User!);

33
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs

@ -31,7 +31,9 @@ public static class OpenIddictClientSystemIntegrationExtensions
throw new ArgumentNullException(nameof(builder));
}
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) &&
// Ensure the operating system is supported.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("android")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Create("ios")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.Create("maccatalyst")) &&
!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
@ -40,8 +42,15 @@ public static class OpenIddictClientSystemIntegrationExtensions
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
}
#if !SUPPORTS_ANDROID
// When running on Android, iOS, Mac Catalyst or macOS, ensure the version compiled for
// these platforms is used to prevent the generic/non-OS specific TFM from being used.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("android")))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#if !SUPPORTS_APPKIT
// When running on iOS, Mac Catalyst or macOS, ensure the version compiled for these platforms is used.
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
@ -54,6 +63,24 @@ public static class OpenIddictClientSystemIntegrationExtensions
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#if SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON
// Ensure the operating system version is supported.
if ((OperatingSystem.IsAndroid() && !OperatingSystem.IsAndroidVersionAtLeast(21)) ||
(OperatingSystem.IsIOS() && !OperatingSystem.IsIOSVersionAtLeast(12)) ||
(OperatingSystem.IsMacCatalyst() && !OperatingSystem.IsMacCatalystVersionAtLeast(13, 1)) ||
(OperatingSystem.IsMacOS() && !OperatingSystem.IsMacOSVersionAtLeast(10, 15)) ||
(OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(7)))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
}
#else
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !IsWindowsVersionAtLeast(7))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
}
#endif
// Note: the OpenIddict activation handler service 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
@ -78,6 +105,8 @@ public static class OpenIddictClientSystemIntegrationExtensions
builder.Services.TryAddSingleton<RequireASWebAuthenticationSession>();
builder.Services.TryAddSingleton<RequireASWebAuthenticationCallbackUrl>();
builder.Services.TryAddSingleton<RequireAuthenticationNonce>();
builder.Services.TryAddSingleton<RequireCustomTabsIntent>();
builder.Services.TryAddSingleton<RequireCustomTabsIntentData>();
builder.Services.TryAddSingleton<RequireEmbeddedWebServerEnabled>();
builder.Services.TryAddSingleton<RequireHttpListenerContext>();
builder.Services.TryAddSingleton<RequireInteractiveSession>();

77
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlerFilters.cs

@ -7,6 +7,7 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Options;
using static OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrationAuthenticationMode;
namespace OpenIddict.Client.SystemIntegration;
@ -31,7 +32,7 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
}
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
if (OpenIddictClientSystemIntegrationHelpers.IsASWebAuthenticationSessionSupported())
if (IsASWebAuthenticationSessionSupported())
{
return new(ContainsASWebAuthenticationSessionResult(context.Transaction));
}
@ -64,7 +65,7 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
}
#if SUPPORTS_AUTHENTICATION_SERVICES
if (OpenIddictClientSystemIntegrationHelpers.IsASWebAuthenticationSessionSupported())
if (IsASWebAuthenticationSessionSupported())
{
if (!context.Transaction.Properties.TryGetValue(
typeof(OpenIddictClientSystemIntegrationAuthenticationMode).FullName!, out var result) ||
@ -73,7 +74,7 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
mode = _options.CurrentValue.AuthenticationMode.GetValueOrDefault();
}
return new(mode is OpenIddictClientSystemIntegrationAuthenticationMode.ASWebAuthenticationSession);
return new(mode is ASWebAuthenticationSession);
}
#endif
return new(false);
@ -98,6 +99,68 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the custom tabs intent integration was not enabled.
/// </summary>
public sealed class RequireCustomTabsIntent : IOpenIddictClientHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options;
public RequireCustomTabsIntent(IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <inheritdoc/>
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
if (IsCustomTabsIntentSupported())
{
if (!context.Transaction.Properties.TryGetValue(
typeof(OpenIddictClientSystemIntegrationAuthenticationMode).FullName!, out var result) ||
result is not OpenIddictClientSystemIntegrationAuthenticationMode mode)
{
mode = _options.CurrentValue.AuthenticationMode.GetValueOrDefault();
}
return new(mode is CustomTabsIntent);
}
#endif
return new(false);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no
/// custom tabs intent data can be found in the transaction properties.
/// </summary>
public sealed class RequireCustomTabsIntentData : IOpenIddictClientHandlerFilter<BaseContext>
{
/// <inheritdoc/>
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
if (IsCustomTabsIntentSupported())
{
return new(ContainsCustomTabsIntentData(context.Transaction));
}
[MethodImpl(MethodImplOptions.NoInlining)]
static bool ContainsCustomTabsIntentData(OpenIddictClientTransaction transaction)
=> transaction.GetCustomTabsIntentData() is not null;
#endif
return new(false);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the embedded web server was not enabled.
/// </summary>
@ -197,7 +260,7 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
mode = _options.CurrentValue.AuthenticationMode.GetValueOrDefault();
}
return new(mode is OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser);
return new(mode is SystemBrowser);
}
}
@ -221,7 +284,7 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
}
#if SUPPORTS_WINDOWS_RUNTIME
if (OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported())
if (IsWebAuthenticationBrokerSupported())
{
if (!context.Transaction.Properties.TryGetValue(
typeof(OpenIddictClientSystemIntegrationAuthenticationMode).FullName!, out var result) ||
@ -230,7 +293,7 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
mode = _options.CurrentValue.AuthenticationMode.GetValueOrDefault();
}
return new(mode is OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker);
return new(mode is WebAuthenticationBroker);
}
#endif
return new(false);
@ -252,7 +315,7 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters
}
#if SUPPORTS_WINDOWS_RUNTIME
if (OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported())
if (IsWebAuthenticationBrokerSupported())
{
return new(ContainsWebAuthenticationResult(context.Transaction));
}

132
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs

@ -12,12 +12,17 @@ using System.Text;
using Microsoft.Extensions.Primitives;
using OpenIddict.Extensions;
#if SUPPORTS_AUTHENTICATION_SERVICES
using AuthenticationServices;
#if SUPPORTS_ANDROID
using Android.Content;
using NativeUri = Android.Net.Uri;
#endif
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
using AndroidX.Browser.CustomTabs;
#endif
#if SUPPORTS_FOUNDATION
using Foundation;
#if SUPPORTS_AUTHENTICATION_SERVICES
using AuthenticationServices;
#endif
#if SUPPORTS_APPKIT
@ -41,7 +46,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
/*
* Authorization request processing:
*/
InvokeASWebAuthenticationSession.Descriptor,
StartASWebAuthenticationSession.Descriptor,
LaunchCustomTabsIntent.Descriptor,
InvokeWebAuthenticationBroker.Descriptor,
LaunchSystemBrowser.Descriptor,
@ -51,6 +57,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
ExtractGetOrPostHttpListenerRequest<ExtractRedirectionRequestContext>.Descriptor,
ExtractProtocolActivationParameters<ExtractRedirectionRequestContext>.Descriptor,
ExtractASWebAuthenticationCallbackUrlData<ExtractRedirectionRequestContext>.Descriptor,
ExtractCustomTabsIntentData<ExtractRedirectionRequestContext>.Descriptor,
ExtractWebAuthenticationResultData<ExtractRedirectionRequestContext>.Descriptor,
/*
@ -61,18 +68,19 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
ProcessEmptyHttpResponse.Descriptor,
ProcessProtocolActivationResponse<ApplyRedirectionResponseContext>.Descriptor,
ProcessASWebAuthenticationSessionResponse<ApplyRedirectionResponseContext>.Descriptor,
ProcessCustomTabsIntentResponse<ApplyRedirectionResponseContext>.Descriptor,
ProcessWebAuthenticationResultResponse<ApplyRedirectionResponseContext>.Descriptor
]);
/// <summary>
/// Contains the logic responsible for initiating authorization requests using the web authentication broker.
/// Contains the logic responsible for initiating authorization requests using an AS web authentication session.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public class InvokeASWebAuthenticationSession : IOpenIddictClientHandler<ApplyAuthorizationRequestContext>
public class StartASWebAuthenticationSession : IOpenIddictClientHandler<ApplyAuthorizationRequestContext>
{
private readonly OpenIddictClientSystemIntegrationService _service;
public InvokeASWebAuthenticationSession(OpenIddictClientSystemIntegrationService service)
public StartASWebAuthenticationSession(OpenIddictClientSystemIntegrationService service)
=> _service = service ?? throw new ArgumentNullException(nameof(service));
/// <summary>
@ -82,7 +90,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyAuthorizationRequestContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireASWebAuthenticationSession>()
.UseSingletonHandler<InvokeASWebAuthenticationSession>()
.UseSingletonHandler<StartASWebAuthenticationSession>()
.SetOrder(100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -108,7 +116,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
return;
}
if (!OpenIddictClientSystemIntegrationHelpers.IsASWebAuthenticationSessionSupported())
if (!IsASWebAuthenticationSessionSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
}
@ -164,8 +172,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
{
#pragma warning disable CA1416
session.PresentationContextProvider = new ASWebAuthenticationPresentationContext(
OpenIddictClientSystemIntegrationHelpers.GetCurrentUIWindow() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0447)));
GetCurrentUIWindow() ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0447)));
#pragma warning restore CA1416
}
#endif
@ -187,8 +194,9 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// Since the result of this operation is known by the time the task signaled by ASWebAuthenticationSession
// returns, canceled demands can directly be handled and surfaced here, as part of the challenge handling.
catch (NSErrorException exception) when (exception.Error.Code is
(int) ASWebAuthenticationSessionErrorCode.CanceledLogin)
catch (NSErrorException exception) when (exception.Error is {
Domain: "com.apple.AuthenticationServices.WebAuthenticationSession",
Code : (int) ASWebAuthenticationSessionErrorCode.CanceledLogin })
{
context.Reject(
error: Errors.AccessDenied,
@ -211,7 +219,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
await _service.HandleASWebAuthenticationCallbackUrlAsync(url, context.CancellationToken);
context.HandleRequest();
return;
#pragma warning restore CA1416
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
#endif
@ -227,6 +234,74 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
#endif
}
/// <summary>
/// Contains the logic responsible for initiating authorization requests using a custom tabs intent.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public class LaunchCustomTabsIntent : IOpenIddictClientHandler<ApplyAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyAuthorizationRequestContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireCustomTabsIntent>()
.UseSingletonHandler<LaunchCustomTabsIntent>()
.SetOrder(100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("android21.0")]
#pragma warning disable CS1998
public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
#pragma warning restore CS1998
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
if (string.IsNullOrEmpty(context.RedirectUri))
{
return;
}
if (!IsCustomTabsIntentSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
}
using var builder = new CustomTabsIntent.Builder();
using var intent = builder.Build();
// Note: using ActivityFlags.NewTask is required when
// creating intents without a parent activity attached.
intent.Intent.AddFlags(ActivityFlags.NewTask);
// Note: unlike iOS's ASWebAuthenticationSession or UWP's WebAuthenticationBroker,
// Android's CustomTabsIntent API doesn't support specifying a "target" URI and uses
// an asynchronous and isolated model that doesn't allow tracking the current URI.
//
// As such, the callback request can only be handled at a later stage by creating a
// custom activity responsible for handling callback URIs pointing to a custom scheme.
intent.LaunchUrl(Application.Context, NativeUri.Parse(OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri)!);
context.HandleRequest();
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
#endif
}
}
/// <summary>
/// Contains the logic responsible for initiating authorization requests using the web authentication broker.
/// Note: this handler is not used when the user session is not interactive.
@ -277,8 +352,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// incompatible application model (e.g WinUI 3.0), the presence of a CoreWindow is verified here.
//
// See https://github.com/microsoft/WindowsAppSDK/issues/398 for more information.
if (!OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported() ||
CoreWindow.GetForCurrentThread() is null)
if (!IsWebAuthenticationBrokerSupported() || CoreWindow.GetForCurrentThread() is null)
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392));
}
@ -410,35 +484,41 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// Runtime APIs and favor the Launcher.LaunchUriAsync() API when it's offered by the platform.
#if SUPPORTS_WINDOWS_RUNTIME
if (OpenIddictClientSystemIntegrationHelpers.IsUriLauncherSupported() && await
OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithWindowsRuntimeAsync(uri))
if (IsUriLauncherSupported() && await TryLaunchBrowserWithWindowsRuntimeAsync(uri))
{
context.HandleRequest();
return;
}
#endif
if (await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithShellExecuteAsync(uri))
if (await TryLaunchBrowserWithShellExecuteAsync(uri))
{
context.HandleRequest();
return;
}
}
#if SUPPORTS_APPKIT
if (OperatingSystem.IsMacOS() && await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithNSWorkspaceAsync(uri))
#if SUPPORTS_ANDROID
if (OperatingSystem.IsAndroid() && TryLaunchBrowserWithGenericIntent(uri))
{
context.HandleRequest();
return;
}
#elif SUPPORTS_UIKIT
if (OperatingSystem.IsIOS() && await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithUIApplicationAsync(uri))
#endif
#if SUPPORTS_UIKIT
if ((OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst()) && await TryLaunchBrowserWithUIApplicationAsync(uri))
{
context.HandleRequest();
return;
}
#elif SUPPORTS_APPKIT
if (OperatingSystem.IsMacOS() && TryLaunchBrowserWithNSWorkspace(uri))
{
context.HandleRequest();
return;
}
#endif
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) &&
await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithXdgOpenAsync(uri))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && await TryLaunchBrowserWithXdgOpenAsync(uri))
{
context.HandleRequest();
return;

131
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs

@ -12,12 +12,17 @@ using System.Text;
using Microsoft.Extensions.Primitives;
using OpenIddict.Extensions;
#if SUPPORTS_AUTHENTICATION_SERVICES
using AuthenticationServices;
#if SUPPORTS_ANDROID
using Android.Content;
using NativeUri = Android.Net.Uri;
#endif
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
using AndroidX.Browser.CustomTabs;
#endif
#if SUPPORTS_FOUNDATION
using Foundation;
#if SUPPORTS_AUTHENTICATION_SERVICES
using AuthenticationServices;
#endif
#if SUPPORTS_APPKIT
@ -41,7 +46,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
/*
* Logout request processing:
*/
InvokeASWebAuthenticationSession.Descriptor,
StartASWebAuthenticationSession.Descriptor,
LaunchCustomTabsIntent.Descriptor,
InvokeWebAuthenticationBroker.Descriptor,
LaunchSystemBrowser.Descriptor,
@ -51,6 +57,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
ExtractGetOrPostHttpListenerRequest<ExtractPostLogoutRedirectionRequestContext>.Descriptor,
ExtractProtocolActivationParameters<ExtractPostLogoutRedirectionRequestContext>.Descriptor,
ExtractASWebAuthenticationCallbackUrlData<ExtractPostLogoutRedirectionRequestContext>.Descriptor,
ExtractCustomTabsIntentData<ExtractPostLogoutRedirectionRequestContext>.Descriptor,
ExtractWebAuthenticationResultData<ExtractPostLogoutRedirectionRequestContext>.Descriptor,
/*
@ -61,18 +68,19 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
ProcessEmptyHttpResponse.Descriptor,
ProcessProtocolActivationResponse<ApplyPostLogoutRedirectionResponseContext>.Descriptor,
ProcessASWebAuthenticationSessionResponse<ApplyPostLogoutRedirectionResponseContext>.Descriptor,
ProcessCustomTabsIntentResponse<ApplyPostLogoutRedirectionResponseContext>.Descriptor,
ProcessWebAuthenticationResultResponse<ApplyPostLogoutRedirectionResponseContext>.Descriptor
]);
/// <summary>
/// Contains the logic responsible for initiating authorization requests using the web authentication broker.
/// Contains the logic responsible for initiating logout requests using an AS web authentication session.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public class InvokeASWebAuthenticationSession : IOpenIddictClientHandler<ApplyLogoutRequestContext>
public class StartASWebAuthenticationSession : IOpenIddictClientHandler<ApplyLogoutRequestContext>
{
private readonly OpenIddictClientSystemIntegrationService _service;
public InvokeASWebAuthenticationSession(OpenIddictClientSystemIntegrationService service)
public StartASWebAuthenticationSession(OpenIddictClientSystemIntegrationService service)
=> _service = service ?? throw new ArgumentNullException(nameof(service));
/// <summary>
@ -82,7 +90,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyLogoutRequestContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireASWebAuthenticationSession>()
.UseSingletonHandler<InvokeASWebAuthenticationSession>()
.UseSingletonHandler<StartASWebAuthenticationSession>()
.SetOrder(100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -108,7 +116,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
return;
}
if (!OpenIddictClientSystemIntegrationHelpers.IsASWebAuthenticationSessionSupported())
if (!IsASWebAuthenticationSessionSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
}
@ -164,8 +172,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
{
#pragma warning disable CA1416
session.PresentationContextProvider = new ASWebAuthenticationPresentationContext(
OpenIddictClientSystemIntegrationHelpers.GetCurrentUIWindow() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0447)));
GetCurrentUIWindow() ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0447)));
#pragma warning restore CA1416
}
#endif
@ -187,8 +194,9 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// Since the result of this operation is known by the time the task signaled by ASWebAuthenticationSession
// returns, canceled demands can directly be handled and surfaced here, as part of the challenge handling.
catch (NSErrorException exception) when (exception.Error.Code is
(int) ASWebAuthenticationSessionErrorCode.CanceledLogin)
catch (NSErrorException exception) when (exception.Error is {
Domain: "com.apple.AuthenticationServices.WebAuthenticationSession",
Code : (int) ASWebAuthenticationSessionErrorCode.CanceledLogin })
{
context.Reject(
error: Errors.AccessDenied,
@ -226,6 +234,74 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
#endif
}
/// <summary>
/// Contains the logic responsible for initiating logout requests using a custom tabs intent.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public class LaunchCustomTabsIntent : IOpenIddictClientHandler<ApplyLogoutRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyLogoutRequestContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireCustomTabsIntent>()
.UseSingletonHandler<LaunchCustomTabsIntent>()
.SetOrder(100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("android21.0")]
#pragma warning disable CS1998
public async ValueTask HandleAsync(ApplyLogoutRequestContext context)
#pragma warning restore CS1998
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
if (string.IsNullOrEmpty(context.PostLogoutRedirectUri))
{
return;
}
if (!IsCustomTabsIntentSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
}
using var builder = new CustomTabsIntent.Builder();
using var intent = builder.Build();
// Note: using ActivityFlags.NewTask is required when
// creating intents without a parent activity attached.
intent.Intent.AddFlags(ActivityFlags.NewTask);
// Note: unlike iOS's ASWebAuthenticationSession or UWP's WebAuthenticationBroker,
// Android's CustomTabsIntent API doesn't support specifying a "target" URI and uses
// an asynchronous and isolated model that doesn't allow tracking the current URI.
//
// As such, the callback request can only be handled at a later stage by creating a
// custom activity responsible for handling callback URIs pointing to a custom scheme.
intent.LaunchUrl(Application.Context, NativeUri.Parse(OpenIddictHelpers.AddQueryStringParameters(
uri: new Uri(context.EndSessionEndpoint, UriKind.Absolute),
parameters: context.Transaction.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => new StringValues((string?[]?) parameter.Value))).AbsoluteUri)!);
context.HandleRequest();
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
#endif
}
}
/// <summary>
/// Contains the logic responsible for initiating logout requests using the web authentication broker.
/// Note: this handler is not used when the user session is not interactive.
@ -276,8 +352,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// incompatible application model (e.g WinUI 3.0), the presence of a CoreWindow is verified here.
//
// See https://github.com/microsoft/WindowsAppSDK/issues/398 for more information.
if (!OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported() ||
CoreWindow.GetForCurrentThread() is null)
if (!IsWebAuthenticationBrokerSupported() || CoreWindow.GetForCurrentThread() is null)
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392));
}
@ -409,35 +484,41 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// Runtime APIs and favor the Launcher.LaunchUriAsync() API when it's offered by the platform.
#if SUPPORTS_WINDOWS_RUNTIME
if (OpenIddictClientSystemIntegrationHelpers.IsUriLauncherSupported() && await
OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithWindowsRuntimeAsync(uri))
if (IsUriLauncherSupported() && await TryLaunchBrowserWithWindowsRuntimeAsync(uri))
{
context.HandleRequest();
return;
}
#endif
if (await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithShellExecuteAsync(uri))
if (await TryLaunchBrowserWithShellExecuteAsync(uri))
{
context.HandleRequest();
return;
}
}
#if SUPPORTS_APPKIT
if (OperatingSystem.IsMacOS() && await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithNSWorkspaceAsync(uri))
#if SUPPORTS_ANDROID
if (OperatingSystem.IsAndroid() && TryLaunchBrowserWithGenericIntent(uri))
{
context.HandleRequest();
return;
}
#elif SUPPORTS_UIKIT
if (OperatingSystem.IsIOS() && await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithUIApplicationAsync(uri))
#endif
#if SUPPORTS_UIKIT
if ((OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst()) && await TryLaunchBrowserWithUIApplicationAsync(uri))
{
context.HandleRequest();
return;
}
#elif SUPPORTS_APPKIT
if (OperatingSystem.IsMacOS() && TryLaunchBrowserWithNSWorkspace(uri))
{
context.HandleRequest();
return;
}
#endif
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) &&
await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithXdgOpenAsync(uri))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && await TryLaunchBrowserWithXdgOpenAsync(uri))
{
context.HandleRequest();
return;

152
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs

@ -24,6 +24,10 @@ using static OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrati
using IHostApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime;
#endif
#if SUPPORTS_ANDROID
using NativeUri = Android.Net.Uri;
#endif
#if SUPPORTS_FOUNDATION
using Foundation;
#endif
@ -44,6 +48,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
ResolveRequestUriFromHttpListenerRequest.Descriptor,
ResolveRequestUriFromProtocolActivation.Descriptor,
ResolveRequestUriFromASWebAuthenticationCallbackUrl.Descriptor,
ResolveRequestUriFromCustomTabsIntentData.Descriptor,
ResolveRequestUriFromWebAuthenticationResult.Descriptor,
InferEndpointTypeFromDynamicAddress.Descriptor,
RejectUnknownHttpRequests.Descriptor,
@ -254,6 +259,49 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
}
}
/// <summary>
/// Contains the logic responsible for resolving the request URI from the custom tabs intent data.
/// Note: this handler is not used when the OpenID Connect request is not an custom tabs intent callback.
/// </summary>
public sealed class ResolveRequestUriFromCustomTabsIntentData : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireCustomTabsIntentData>()
.UseSingletonHandler<ResolveRequestUriFromCustomTabsIntentData>()
.SetOrder(ResolveRequestUriFromASWebAuthenticationCallbackUrl.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("android21.0")]
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_ANDROID
(context.BaseUri, context.RequestUri) = context.Transaction.GetCustomTabsIntentData() switch
{
NativeUri url when Uri.TryCreate(url.ToString(), UriKind.Absolute, out Uri? uri) => (
BaseUri: new UriBuilder(uri) { Path = null, Query = null, Fragment = null }.Uri,
RequestUri: uri),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0393))
};
return default;
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
#endif
}
}
/// <summary>
/// Contains the logic responsible for resolving the request URI from the web authentication result.
/// Note: this handler is not used when the OpenID Connect request is not a web authentication result.
@ -267,7 +315,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireWebAuthenticationResult>()
.UseSingletonHandler<ResolveRequestUriFromWebAuthenticationResult>()
.SetOrder(ResolveRequestUriFromASWebAuthenticationCallbackUrl.Descriptor.Order + 1_000)
.SetOrder(ResolveRequestUriFromCustomTabsIntentData.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -688,6 +736,70 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect requests from the callback URL of a custom tabs intent.
/// Note: this handler is not used when the OpenID Connect request is not a custom tabs intent result.
/// </summary>
public sealed class ExtractCustomTabsIntentData<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseValidatingContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireCustomTabsIntentData>()
.UseSingletonHandler<ExtractCustomTabsIntentData<TContext>>()
.SetOrder(ExtractProtocolActivationParameters<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("android21.0")]
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_ANDROID
if (context.Transaction.GetCustomTabsIntentData()
is not NativeUri url || !Uri.TryCreate(url.ToString(), UriKind.Absolute, out Uri? uri))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0393));
}
var parameters = new Dictionary<string, StringValues>(StringComparer.Ordinal);
if (!string.IsNullOrEmpty(uri.Query))
{
foreach (var parameter in OpenIddictHelpers.ParseQuery(uri.Query))
{
parameters[parameter.Key] = parameter.Value;
}
}
// Note: the fragment is always processed after the query string to ensure that
// parameters extracted from the fragment are preferred to parameters extracted
// from the query string when they are present in both parts.
if (!string.IsNullOrEmpty(uri.Fragment))
{
foreach (var parameter in OpenIddictHelpers.ParseFragment(uri.Fragment))
{
parameters[parameter.Key] = parameter.Value;
}
}
context.Transaction.Request = new OpenIddictRequest(parameters);
return default;
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
#endif
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect
/// requests from the response data of a web authentication result.
@ -702,7 +814,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireWebAuthenticationResult>()
.UseSingletonHandler<ExtractWebAuthenticationResultData<TContext>>()
.SetOrder(ExtractASWebAuthenticationCallbackUrlData<TContext>.Descriptor.Order + 1_000)
.SetOrder(ExtractCustomTabsIntentData<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -2521,6 +2633,42 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
}
}
/// <summary>
/// Contains the logic responsible for marking OpenID Connect responses returned via a custom tabs intent web as processed.
/// </summary>
public sealed class ProcessCustomTabsIntentResponse<TContext> : IOpenIddictClientHandler<TContext>
where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireCustomTabsIntentData>()
.UseSingletonHandler<ProcessCustomTabsIntentResponse<TContext>>()
.SetOrder(int.MaxValue)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// For both protocol activations (initial or redirected) and web-view-like results,
// no proper response can be generated and eventually displayed to the user. In this
// case, simply stop processing the response and mark the request as fully handled.
//
// Note: this logic applies to both successful and errored responses.
context.HandleRequest();
return default;
}
}
/// <summary>
/// Contains the logic responsible for marking OpenID Connect responses
/// returned via AS web authentication callback URLs as processed.

85
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs

@ -13,6 +13,11 @@ using System.Runtime.Versioning;
using System.Security.Principal;
using OpenIddict.Extensions;
#if SUPPORTS_ANDROID
using Android.Content;
using NativeUri = Android.Net.Uri;
#endif
#if SUPPORTS_FOUNDATION
using Foundation;
#endif
@ -68,6 +73,17 @@ public static class OpenIddictClientSystemIntegrationHelpers
=> transaction.GetProperty<NSUrl>(typeof(NSUrl).FullName!);
#endif
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
/// <summary>
/// Gets the custom tabs intent data associated with the current context.
/// </summary>
/// <param name="transaction">The transaction instance.</param>
/// <returns>The <see cref="NativeUri"/> instance or <see langword="null"/> if it couldn't be found.</returns>
[SupportedOSPlatform("android21.0")]
public static NativeUri? GetCustomTabsIntentData(this OpenIddictClientTransaction transaction)
=> transaction.GetProperty<NativeUri>(typeof(NativeUri).FullName!);
#endif
#if SUPPORTS_WINDOWS_RUNTIME
/// <summary>
/// Gets the <see cref="WebAuthenticationResult"/> associated with the current context.
@ -128,6 +144,19 @@ public static class OpenIddictClientSystemIntegrationHelpers
=> false;
#endif
/// <summary>
/// Determines whether the CustomTabsIntent API is supported on this platform.
/// </summary>
/// <returns><see langword="true"/> if the CustomTabsIntent API is supported, <see langword="false"/> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SupportedOSPlatformGuard("android21.0")]
internal static bool IsCustomTabsIntentSupported()
#if SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON
=> OperatingSystem.IsAndroidVersionAtLeast(21);
#else
=> false;
#endif
/// <summary>
/// Determines whether the Windows Runtime APIs are supported on this platform.
/// </summary>
@ -422,6 +451,33 @@ public static class OpenIddictClientSystemIntegrationHelpers
}
}
#if SUPPORTS_ANDROID
/// <summary>
/// Starts the system browser using <see href="NSWorkspace"/>.
/// </summary>
/// <param name="uri">The <see cref="Uri"/> to use.</param>
/// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns>
[SupportedOSPlatform("android")]
internal static bool TryLaunchBrowserWithGenericIntent(Uri uri)
{
using var intent = new Intent(Intent.ActionView);
intent.AddFlags(ActivityFlags.NewTask);
intent.SetData(NativeUri.Parse(uri.AbsoluteUri));
try
{
Application.Context.StartActivity(intent);
return true;
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
return false;
}
}
#endif
#if SUPPORTS_APPKIT
/// <summary>
/// Starts the system browser using <see href="NSWorkspace"/>.
@ -429,8 +485,18 @@ public static class OpenIddictClientSystemIntegrationHelpers
/// <param name="uri">The <see cref="Uri"/> to use.</param>
/// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns>
[SupportedOSPlatform("macos")]
internal static ValueTask<bool> TryLaunchBrowserWithNSWorkspaceAsync(Uri uri)
=> new(NSWorkspace.SharedWorkspace.OpenUrl(new NSUrl(uri.AbsoluteUri)));
internal static bool TryLaunchBrowserWithNSWorkspace(Uri uri)
{
try
{
return NSWorkspace.SharedWorkspace.OpenUrl(new NSUrl(uri.AbsoluteUri));
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
return false;
}
}
#endif
#if SUPPORTS_UIKIT
@ -440,8 +506,19 @@ public static class OpenIddictClientSystemIntegrationHelpers
/// <param name="uri">The <see cref="Uri"/> to use.</param>
/// <returns><see langword="true"/> if the browser could be started, <see langword="false"/> otherwise.</returns>
[SupportedOSPlatform("ios")]
internal static ValueTask<bool> TryLaunchBrowserWithUIApplicationAsync(Uri uri)
=> new(UIApplication.SharedApplication.OpenUrlAsync(new NSUrl(uri.AbsoluteUri), new UIApplicationOpenUrlOptions()));
[SupportedOSPlatform("maccatalyst")]
internal static async ValueTask<bool> TryLaunchBrowserWithUIApplicationAsync(Uri uri)
{
try
{
return await UIApplication.SharedApplication.OpenUrlAsync(new NSUrl(uri.AbsoluteUri), new UIApplicationOpenUrlOptions());
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
return false;
}
}
#endif
/// <summary>

2
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs

@ -155,7 +155,7 @@ public sealed class OpenIddictClientSystemIntegrationHttpListener : BackgroundSe
// configured to reject such requests) without requiring administrator rights.
//
// See https://www.rfc-editor.org/rfc/rfc8252#section-8.3 for more information.
if (OpenIddictClientSystemIntegrationHelpers.IsWindowsVersionAtLeast(10, 0, 10586))
if (IsWindowsVersionAtLeast(10, 0, 10586))
{
if (Socket.OSSupportsIPv4)
{

18
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs

@ -12,6 +12,11 @@ using System.Security.Principal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
#if SUPPORTS_ANDROID
using Android.Content;
using NativeUri = Android.Net.Uri;
#endif
#if SUPPORTS_FOUNDATION
using Foundation;
#endif
@ -58,6 +63,19 @@ public sealed class OpenIddictClientSystemIntegrationService
OpenIddictClientSystemIntegrationActivation activation, CancellationToken cancellationToken = default)
=> HandleRequestAsync(activation ?? throw new ArgumentNullException(nameof(activation)), cancellationToken);
#if SUPPORTS_ANDROID && SUPPORTS_ANDROIDX_BROWSER
/// <summary>
/// Handles the specified intent.
/// </summary>
/// <param name="intent">The intent.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="intent"/> is <see langword="null"/>.</exception>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public Task HandleCustomTabsIntentAsync(Intent intent, CancellationToken cancellationToken = default)
=> HandleRequestAsync(intent?.Data ?? throw new ArgumentNullException(nameof(intent)), cancellationToken);
#endif
/// <summary>
/// Handles the specified HTTP request.
/// </summary>

1
src/OpenIddict/OpenIddict.csproj

@ -4,6 +4,7 @@
<TargetFrameworks>
$(NetFrameworkTargetFrameworks);
$(NetCoreTargetFrameworks);
$(NetCoreAndroidTargetFrameworks);
$(NetCoreIOSTargetFrameworks);
$(NetCoreMacCatalystTargetFrameworks);
$(NetCoreMacOSTargetFrameworks);

Loading…
Cancel
Save