diff --git a/Directory.Build.props b/Directory.Build.props
index 1287cab9..f949ca97 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -118,17 +118,17 @@
- net8.0-ios17.2
+ net8.0-ios17.5
- net8.0-maccatalyst17.2
+ net8.0-maccatalyst17.5
- net8.0-macos14.2
+ net8.0-macos14.5
Exe
net8.0-windows10.0.19041
- $(TargetFrameworks);net8.0-ios17.2
- $(TargetFrameworks);net8.0-maccatalyst17.2
+ $(TargetFrameworks);net8.0-ios17.5
+ $(TargetFrameworks);net8.0-maccatalyst17.5
true
net8.0
true
diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 702e67ee..ee173139 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1684,7 +1684,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
The generic version of the OpenIddict.Client.SystemIntegration package cannot be used on this platform. Make sure your application is referencing the correct version by using the appropriate OS-specific TFM (e.g on macOS, 'net8.0-macos10.15').
- An HTTP/HTTPS redirect_uri or post_logout_redirect_uri cannot be used when using AS web authentication sessions. Make sure you're using a custom protocol scheme for all the callback URIs attached to the client registration.
+ An HTTP redirect_uri or post_logout_redirect_uri cannot be used when using AS web authentication sessions. Make sure you're using a custom protocol scheme for all the callback URIs attached to the client registration. Alternatively, you can register an associated domain and use an HTTPS redirect_uri or post_logout_redirect_uri pointing to that domain (supported only on iOS 17.4+, Mac Catalyst 17.4+ and macOS 14.4+).
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.
@@ -2874,6 +2874,12 @@ This may indicate that the hashed entry is corrupted or malformed.
The revocation request was rejected by the remote authorization server: {Response}.
+
+ An error was returned by ASWebAuthenticationSession while trying to start a challenge operation.
+
+
+ An error was returned by ASWebAuthenticationSession while trying to start a sign-out operation.
+
https://documentation.openiddict.com/errors/{0}
diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs
index c38c61c5..d0adb393 100644
--- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs
+++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Authentication.cs
@@ -9,6 +9,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using OpenIddict.Extensions;
@@ -111,7 +112,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
- if (string.IsNullOrEmpty(context.RedirectUri))
+ if (string.IsNullOrEmpty(context.RedirectUri) ||
+ !Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri))
{
return;
}
@@ -121,13 +123,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
}
- if (!Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) ||
- (string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)))
- {
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0450));
- }
-
var source = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
// OpenIddict represents the complete interactive authentication dance as a two-phase process:
@@ -143,30 +138,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// doesn't return until the specified callback URI is reached or the modal closed by the user.
// To accomodate OpenIddict's model, successful results are processed as any other callback request.
- using var session = new ASWebAuthenticationSession(
- url: new NSUrl(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),
- callbackUrlScheme: uri.Scheme,
- completionHandler: (url, error) =>
- {
- if (url is not null)
- {
- source.SetResult(url);
- }
-
- else if (error is not null)
- {
- source.SetException(new NSErrorException(error));
- }
-
- else
- {
- source.SetException(new InvalidOperationException(SR.GetResourceString(SR.ID0448)));
- }
- });
+ using var session = CreateASWebAuthenticationSession();
#if SUPPORTS_PRESENTATION_CONTEXT_PROVIDER
// On iOS 13.0 and higher, a presentation context provider returning the UI window to
@@ -211,8 +183,10 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
return;
}
- catch (NSErrorException)
+ catch (NSErrorException exception)
{
+ context.Logger.LogError(exception, SR.GetResourceString(SR.ID6231));
+
context.Reject(
error: Errors.ServerError,
description: SR.GetResourceString(SR.ID2136),
@@ -224,6 +198,72 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
await _service.HandleASWebAuthenticationCallbackUrlAsync(url, context.CancellationToken);
context.HandleRequest();
return;
+
+ ASWebAuthenticationSession CreateASWebAuthenticationSession()
+ {
+ // Starting with iOS 17.4+, Mac Catalyst 17.4+ and macOS 14.4+, the ASWebAuthenticationSession initializer
+ // accepting a custom scheme string is now deprecated and is replaced by an initializer taking an
+ // ASWebAuthenticationSessionCallback object, which allows supporting HTTPS callback URIs/Universal Links.
+ if (OperatingSystem.IsIOSVersionAtLeast(17, 4) ||
+ OperatingSystem.IsMacCatalystVersionAtLeast(17, 4) ||
+ OperatingSystem.IsMacOSVersionAtLeast(14, 4))
+ {
+ return new ASWebAuthenticationSession(
+ url: CreateUrl(),
+ callback: uri switch
+ {
+ // Note: non-default ports are not allowed in associated domains, that are
+ // required to use HTTPS URIs with the ASWebAuthenticationSessionCallback API.
+ Uri { IsDefaultPort: true } uri when string.Equals(
+ uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
+ => ASWebAuthenticationSessionCallback.Create(
+ httpsHost: uri.Host,
+ path : uri.AbsolutePath is ['/', _, ..] ? uri.AbsolutePath[1..] : uri.AbsoluteUri),
+
+ Uri uri when !string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
+ => ASWebAuthenticationSessionCallback.Create(uri.Scheme),
+
+ // HTTP-only callback URIs and HTTPS URIs using non-default ports
+ // are not supported by the ASWebAuthenticationSessionCallback API.
+ _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0450))
+ },
+ completionHandler: HandleCallback);
+ }
+
+ // On older platforms, only callback URIs using a custom scheme can be used.
+ if (string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0450));
+ }
+
+ return new ASWebAuthenticationSession(CreateUrl(), uri.Scheme, HandleCallback);
+
+ NSUrl CreateUrl() => new(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);
+
+ void HandleCallback(NSUrl? url, NSError? error)
+ {
+ if (url is not null)
+ {
+ source.SetResult(url);
+ }
+
+ else if (error is not null)
+ {
+ source.SetException(new NSErrorException(error));
+ }
+
+ else
+ {
+ source.SetException(new InvalidOperationException(SR.GetResourceString(SR.ID0448)));
+ }
+ }
+ }
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
#endif
diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs
index 84493d89..ac0067de 100644
--- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs
+++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.Session.cs
@@ -9,6 +9,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using OpenIddict.Extensions;
@@ -111,7 +112,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
- if (string.IsNullOrEmpty(context.PostLogoutRedirectUri))
+ if (string.IsNullOrEmpty(context.PostLogoutRedirectUri) ||
+ !Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri))
{
return;
}
@@ -121,13 +123,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
}
- if (!Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri) ||
- (string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)))
- {
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0450));
- }
-
var source = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
// OpenIddict represents the complete interactive logout dance as a two-phase process:
@@ -143,30 +138,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// doesn't return until the specified callback URI is reached or the modal closed by the user.
// To accomodate OpenIddict's model, successful results are processed as any other callback request.
- using var session = new ASWebAuthenticationSession(
- url: new NSUrl(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),
- callbackUrlScheme: uri.Scheme,
- completionHandler: (url, error) =>
- {
- if (url is not null)
- {
- source.SetResult(url);
- }
-
- else if (error is not null)
- {
- source.SetException(new NSErrorException(error));
- }
-
- else
- {
- source.SetException(new InvalidOperationException(SR.GetResourceString(SR.ID0448)));
- }
- });
+ using var session = CreateASWebAuthenticationSession();
#if SUPPORTS_PRESENTATION_CONTEXT_PROVIDER
// On iOS 13.0 and higher, a presentation context provider returning the UI window to
@@ -211,8 +183,10 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
return;
}
- catch (NSErrorException)
+ catch (NSErrorException exception)
{
+ context.Logger.LogError(exception, SR.GetResourceString(SR.ID6232));
+
context.Reject(
error: Errors.ServerError,
description: SR.GetResourceString(SR.ID2136),
@@ -224,6 +198,72 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
await _service.HandleASWebAuthenticationCallbackUrlAsync(url, context.CancellationToken);
context.HandleRequest();
return;
+
+ ASWebAuthenticationSession CreateASWebAuthenticationSession()
+ {
+ // Starting with iOS 17.4+, Mac Catalyst 17.4+ and macOS 14.4+, the ASWebAuthenticationSession initializer
+ // accepting a custom scheme string is now deprecated and is replaced by an initializer taking an
+ // ASWebAuthenticationSessionCallback object, which allows supporting HTTPS callback URIs/Universal Links.
+ if (OperatingSystem.IsIOSVersionAtLeast(17, 4) ||
+ OperatingSystem.IsMacCatalystVersionAtLeast(17, 4) ||
+ OperatingSystem.IsMacOSVersionAtLeast(14, 4))
+ {
+ return new ASWebAuthenticationSession(
+ url: CreateUrl(),
+ callback: uri switch
+ {
+ // Note: non-default ports are not allowed in associated domains, that are
+ // required to use HTTPS URIs with the ASWebAuthenticationSessionCallback API.
+ Uri { IsDefaultPort: true } uri when string.Equals(
+ uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
+ => ASWebAuthenticationSessionCallback.Create(
+ httpsHost: uri.Host,
+ path : uri.AbsolutePath is ['/', _, ..] ? uri.AbsolutePath[1..] : uri.AbsoluteUri),
+
+ Uri uri when !string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
+ => ASWebAuthenticationSessionCallback.Create(uri.Scheme),
+
+ // HTTP-only callback URIs and HTTPS URIs using non-default ports
+ // are not supported by the ASWebAuthenticationSessionCallback API.
+ _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0450))
+ },
+ completionHandler: HandleCallback);
+ }
+
+ // On older platforms, only callback URIs using a custom scheme can be used.
+ if (string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0450));
+ }
+
+ return new ASWebAuthenticationSession(CreateUrl(), uri.Scheme, HandleCallback);
+
+ NSUrl CreateUrl() => new(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);
+
+ void HandleCallback(NSUrl? url, NSError? error)
+ {
+ if (url is not null)
+ {
+ source.SetResult(url);
+ }
+
+ else if (error is not null)
+ {
+ source.SetException(new NSErrorException(error));
+ }
+
+ else
+ {
+ source.SetException(new InvalidOperationException(SR.GetResourceString(SR.ID0448)));
+ }
+ }
+ }
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
#endif