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..eac47acf5 100644 --- a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m @@ -43,6 +43,31 @@ @interface OIDExternalUserAgentIOS () @end #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 transition, which + hasn't started yet when the completion handler runs. + */ +static const NSTimeInterval kOIDSessionRetentionDelay = 2.0; + +/*! @brief Keeps @c session alive for a while after the caller lets go of 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) { + if (!session) { + return; + } + 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 { UIViewController *_presentingViewController; BOOL _prefersEphemeralSession; @@ -132,6 +157,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 +229,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