Skip to content

Commit

Permalink
Support the new ASWebAuthenticationSessionCallback API on iOS 17.4+/M…
Browse files Browse the repository at this point in the history
…ac Catalyst 17.4+/macOS 14.4+
  • Loading branch information
kevinchalet committed Aug 16, 2024
1 parent cd127b3 commit 290e415
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 78 deletions.
6 changes: 3 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,17 @@

<NetCoreIOSTargetFrameworks
Condition=" '$(NetCoreIOSTargetFrameworks)' == '' And '$(SupportsIOSTargeting)' == 'true' ">
net8.0-ios17.2
net8.0-ios17.5
</NetCoreIOSTargetFrameworks>

<NetCoreMacCatalystTargetFrameworks
Condition=" '$(NetCoreMacCatalystTargetFrameworks)' == '' And '$(SupportsMacCatalystTargeting)' == 'true' ">
net8.0-maccatalyst17.2
net8.0-maccatalyst17.5
</NetCoreMacCatalystTargetFrameworks>

<NetCoreMacOSTargetFrameworks
Condition=" '$(NetCoreMacOSTargetFrameworks)' == '' And '$(SupportsMacOSTargeting)' == 'true' ">
net8.0-macos14.2
net8.0-macos14.5
</NetCoreMacOSTargetFrameworks>

<NetCoreWindowsTargetFrameworks
Expand Down
12 changes: 6 additions & 6 deletions WorkloadRollback.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"microsoft.net.sdk.android": "34.0.113/8.0.100",
"microsoft.net.sdk.ios": "17.2.8053/8.0.100",
"microsoft.net.sdk.maccatalyst": "17.2.8053/8.0.100",
"microsoft.net.sdk.macos": "14.2.8053/8.0.100",
"microsoft.net.sdk.maui": "8.0.61/8.0.100",
"microsoft.net.sdk.tvos": "17.2.8053/8.0.100",
"microsoft.net.sdk.ios": "17.5.8020/8.0.100",
"microsoft.net.sdk.maccatalyst": "17.5.8020/8.0.100",
"microsoft.net.sdk.macos": "14.5.8020/8.0.100",
"microsoft.net.sdk.maui": "8.0.72/8.0.100",
"microsoft.net.sdk.tvos": "17.5.8020/8.0.100",
"microsoft.net.workload.mono.toolchain.current": "8.0.8/8.0.100",
"microsoft.net.workload.emscripten.current": "8.0.8/8.0.100",
"microsoft.net.workload.emscripten.net6": "8.0.8/8.0.100",
"microsoft.net.workload.emscripten.net7": "8.0.8/8.0.100",
"microsoft.net.workload.mono.toolchain.net6": "8.0.8/8.0.100",
"microsoft.net.workload.mono.toolchain.net7": "8.0.8/8.0.100",
"microsoft.net.sdk.aspire": "8.0.2/8.0.100"
"microsoft.net.sdk.aspire": "8.1.0/8.0.100"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks Condition=" '$(SupportsWindowsTargeting)' == 'true' ">net8.0-windows10.0.19041</TargetFrameworks>
<TargetFrameworks Condition=" '$(SupportsIOSTargeting)' == 'true' ">$(TargetFrameworks);net8.0-ios17.2</TargetFrameworks>
<TargetFrameworks Condition=" '$(SupportsMacCatalystTargeting)' == 'true' ">$(TargetFrameworks);net8.0-maccatalyst17.2</TargetFrameworks>
<TargetFrameworks Condition=" '$(SupportsIOSTargeting)' == 'true' ">$(TargetFrameworks);net8.0-ios17.5</TargetFrameworks>
<TargetFrameworks Condition=" '$(SupportsMacCatalystTargeting)' == 'true' ">$(TargetFrameworks);net8.0-maccatalyst17.5</TargetFrameworks>
<UseMaui Condition=" '$(TargetFrameworks)' != '' ">true</UseMaui>
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' ">net8.0</TargetFrameworks>
<SingleProject>true</SingleProject>
Expand Down
8 changes: 7 additions & 1 deletion src/OpenIddict.Abstractions/OpenIddictResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1684,7 +1684,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<value>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').</value>
</data>
<data name="ID0450" xml:space="preserve">
<value>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.</value>
<value>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+).</value>
</data>
<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>
Expand Down Expand Up @@ -2874,6 +2874,12 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
<data name="ID6230" xml:space="preserve">
<value>The revocation request was rejected by the remote authorization server: {Response}.</value>
</data>
<data name="ID6231" xml:space="preserve">
<value>An error was returned by ASWebAuthenticationSession while trying to start a challenge operation.</value>
</data>
<data name="ID6232" xml:space="preserve">
<value>An error was returned by ASWebAuthenticationSession while trying to start a sign-out operation.</value>
</data>
<data name="ID8000" xml:space="preserve">
<value>https://documentation.openiddict.com/errors/{0}</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using OpenIddict.Extensions;

Expand Down Expand Up @@ -111,7 +112,8 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
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;
}
Expand All @@ -121,13 +123,6 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
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<NSUrl>(TaskCreationOptions.RunContinuationsAsynchronously);

// OpenIddict represents the complete interactive authentication dance as a two-phase process:
Expand All @@ -143,30 +138,7 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
// 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
Expand Down Expand Up @@ -211,8 +183,10 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
return;
}

catch (NSErrorException)
catch (NSErrorException exception)
{
context.Logger.LogError(exception, SR.GetResourceString(SR.ID6231));

context.Reject(
error: Errors.ServerError,
description: SR.GetResourceString(SR.ID2136),
Expand All @@ -224,6 +198,72 @@ public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
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
Expand Down
Loading

0 comments on commit 290e415

Please sign in to comment.