From 6dc194fd6b719c90afe292310013a65bc5914c47 Mon Sep 17 00:00:00 2001 From: Worthing ~ <115107835+w-goog@users.noreply.github.com> Date: Wed, 21 Oct 2026 11:42:57 -0700 Subject: [PATCH 1/3] g-orchestrated: keep ASWebAuthenticationSession alive past dismissal --- CHANGELOG.md | 1 + Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50023e728..072dca622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # UNRELEASED - Improved the state validation error messages to distinguish a missing `state` parameter from a mismatched one. The end session message now notes that a provider's `end_session_endpoint` may point at a legacy or SAML single-logout endpoint, which does not echo `state`. Addresses issue #956. - Fixed `OIDExternalUserAgentIOSCustomBrowser` failing the flow with "Unable to open Safari." when `canOpenURL:` returns `NO` for a URL that the system can open, for example an https URL when Safari is restricted and another browser is the default. The flow now fails only if the URL really cannot be opened. Addresses issue #980. +- Fixed a crash when the user cancels sign-in on iOS, caused by `ASWebAuthenticationSession` being deallocated during its completion handler or dismiss animation. Based on [#917](https://github.com/openid/AppAuth-iOS/pull/917) by @Prash88. # 3.0.0 - BREAKING: Updates made to support Xcode 27. ([#972](https://github.com/openid/AppAuth-iOS/pull/972), [#973](https://github.com/openid/AppAuth-iOS/pull/973)) diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m index b6e29bd2a..908cbc815 100644 --- a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m @@ -43,6 +43,27 @@ @interface OIDExternalUserAgentIOS () @end #endif +/*! @brief How long a dismissed session is kept alive. Long enough to outlast its dismiss animation. + */ +static const NSTimeInterval kOIDSessionRetentionDelay = 1.0; + +/*! @brief Keeps @c session alive for a short while after the caller lets go of it. + @discussion ASWebAuthenticationSession can crash if it is deallocated during its own + completion handler or while its dismiss animation is still running (FB12132525). + Holding it on a delayed main-queue block outlives both. + @param session The session to keep alive, or nil. + */ +static void OIDRetainSessionBriefly(ASWebAuthenticationSession *_Nullable session) { + if (!session) { + return; + } + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kOIDSessionRetentionDelay * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + (void)session; + }); +} + @implementation OIDExternalUserAgentIOS { UIViewController *_presentingViewController; BOOL _prefersEphemeralSession; @@ -132,6 +153,7 @@ - (BOOL)presentExternalUserAgentRequest:(id)request if (!strongSelf) { return; } + OIDRetainSessionBriefly(strongSelf->_webAuthenticationVC); strongSelf->_webAuthenticationVC = nil; if (callbackURL) { // The session matches callback URLs more loosely than the flow checks the redirect URL (a @@ -203,6 +225,7 @@ - (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(voi if (webAuthenticationVC) { // dismiss the ASWebAuthenticationSession [webAuthenticationVC cancel]; + OIDRetainSessionBriefly(webAuthenticationVC); if (completion) completion(); } else if (safariVC) { // dismiss the SFSafariViewController From f6d53cef5f5fa050f8f579d5b972d98d51b71d28 Mon Sep 17 00:00:00 2001 From: Worthing ~ <115107835+w-goog@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:14:21 -0700 Subject: [PATCH 2/3] g-orchestrated: start the session retention delay once the main queue is free --- Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m index 908cbc815..732b9e17b 100644 --- a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m @@ -43,25 +43,29 @@ @interface OIDExternalUserAgentIOS () @end #endif -/*! @brief How long a dismissed session is kept alive. Long enough to outlast its dismiss animation. +/*! @brief How long a finished session is kept alive once the main queue is next free. + @discussion This is a heuristic. It needs to outlast the session's dismiss animation, which + has no API to observe. */ -static const NSTimeInterval kOIDSessionRetentionDelay = 1.0; +static const NSTimeInterval kOIDSessionRetentionDelay = 3.0; -/*! @brief Keeps @c session alive for a short while after the caller lets go of it. - @discussion ASWebAuthenticationSession can crash if it is deallocated during its own - completion handler or while its dismiss animation is still running (FB12132525). - Holding it on a delayed main-queue block outlives both. +/*! @brief Keeps @c session alive for a while after the caller lets go of it. + @discussion ASWebAuthenticationSession can crash if it is deallocated during a) its own + completion handler or b) its dismiss animation (FB12132525). The delay starts once the + main queue is free, so a slow completion callback doesn't eat into it. @param session The session to keep alive, or nil. */ static void OIDRetainSessionBriefly(ASWebAuthenticationSession *_Nullable session) { if (!session) { return; } - dispatch_after( - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kOIDSessionRetentionDelay * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - (void)session; - }); + dispatch_async(dispatch_get_main_queue(), ^{ + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kOIDSessionRetentionDelay * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + (void)session; + }); + }); } @implementation OIDExternalUserAgentIOS { From 28a834cb0731b09de6a2ae31e14e178d92297ab8 Mon Sep 17 00:00:00 2001 From: Worthing ~ <115107835+w-goog@users.noreply.github.com> Date: Wed, 21 Oct 2026 15:09:19 -0700 Subject: [PATCH 3/3] g-orchestrated: correct the session retention comment and use a 2s delay --- Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m index 732b9e17b..eac47acf5 100644 --- a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m @@ -44,15 +44,15 @@ @interface OIDExternalUserAgentIOS () #endif /*! @brief How long a finished session is kept alive once the main queue is next free. - @discussion This is a heuristic. It needs to outlast the session's dismiss animation, which - has no API to observe. + @discussion This is a heuristic. It needs to outlast the session's dismiss transition, which + hasn't started yet when the completion handler runs. */ -static const NSTimeInterval kOIDSessionRetentionDelay = 3.0; +static const NSTimeInterval kOIDSessionRetentionDelay = 2.0; /*! @brief Keeps @c session alive for a while after the caller lets go of it. - @discussion ASWebAuthenticationSession can crash if it is deallocated during a) its own - completion handler or b) its dismiss animation (FB12132525). The delay starts once the - main queue is free, so a slow completion callback doesn't eat into it. + @discussion If the session is deallocated before its dismiss transition ends, it crashes + when the transition completes (FB12132525). The delay starts once the main queue is + free, so a slow completion callback doesn't eat into it. @param session The session to keep alive, or nil. */ static void OIDRetainSessionBriefly(ASWebAuthenticationSession *_Nullable session) {