1818 * not triggered by server redirects/JS navigation inside the session and is sporadically
1919 * dropped, leaving authorize() pending forever (#987, #932; openid/AppAuth-iOS#367).
2020 * This agent lets the session intercept the https redirect natively.
21+ *
22+ * Requires the callback host to be an associated domain with the webcredentials service
23+ * type (entitlement + apple-app-site-association). When the association is missing the
24+ * agent transparently falls back to the legacy callbackURLScheme session, preserving
25+ * AppAuth's default behavior.
2126 */
2227API_AVAILABLE (ios(17.4 ))
2328@interface RNAppAuthHTTPSExternalUserAgent : NSObject <OIDExternalUserAgent, ASWebAuthenticationPresentationContextProviding>
@@ -850,8 +855,10 @@ @implementation RNAppAuthHTTPSExternalUserAgent {
850855 NSString *_host;
851856 NSString *_path;
852857 BOOL _externalUserAgentFlowInProgress;
858+ BOOL _didFallBackToLegacySession;
853859 __weak id <OIDExternalUserAgentSession> _session;
854860 ASWebAuthenticationSession *_webAuthenticationSession;
861+ NSURL *_requestURL;
855862}
856863
857864- (instancetype )initWithPresentingViewController : (UIViewController *)presentingViewController
@@ -875,36 +882,76 @@ - (BOOL)presentExternalUserAgentRequest:(id<OIDExternalUserAgentRequest>)request
875882 }
876883 _externalUserAgentFlowInProgress = YES ;
877884 _session = session;
885+ _requestURL = [request externalUserAgentRequestURL ];
878886
879- NSURL *requestURL = [request externalUserAgentRequestURL ];
880- __weak typeof (self) weakSelf = self;
887+ ASWebAuthenticationSession *webAuthenticationSession = [self authenticationSessionWithHTTPSCallback: YES ];
888+ _webAuthenticationSession = webAuthenticationSession;
889+ if ([webAuthenticationSession start ]) {
890+ return YES ;
891+ }
892+ return [self startLegacyFallbackSession ];
893+ }
894+
895+ /* *
896+ * The https callback requires the callback host to be an associated domain with the
897+ * webcredentials service type (entitlement + apple-app-site-association entry). When the
898+ * association is missing or not yet validated, the session refuses to start — either
899+ * start returns NO or the completion handler fires immediately with a non-cancel error.
900+ * In both cases fall back to the legacy callbackURLScheme session, which matches
901+ * AppAuth's default behavior, so sign-in keeps working instead of hard-failing.
902+ */
903+ - (BOOL )startLegacyFallbackSession {
904+ if (_didFallBackToLegacySession) {
905+ return NO ;
906+ }
907+ _didFallBackToLegacySession = YES ;
908+ ASWebAuthenticationSession *fallbackSession = [self authenticationSessionWithHTTPSCallback: NO ];
909+ _webAuthenticationSession = fallbackSession;
910+ return [fallbackSession start ];
911+ }
881912
882- ASWebAuthenticationSessionCallback *callback =
883- [ASWebAuthenticationSessionCallback callbackWithHTTPSHost: _host path: _path];
884- ASWebAuthenticationSession *webAuthenticationSession =
885- [[ASWebAuthenticationSession alloc ] initWithURL: requestURL
886- callback: callback
887- completionHandler: ^(NSURL *_Nullable callbackURL, NSError *_Nullable error) {
913+ - (ASWebAuthenticationSession *)authenticationSessionWithHTTPSCallback : (BOOL )useHTTPSCallback {
914+ __weak typeof (self) weakSelf = self;
915+ void (^completionHandler)(NSURL *_Nullable, NSError *_Nullable) =
916+ ^(NSURL *_Nullable callbackURL, NSError *_Nullable error) {
888917 typeof (self) strongSelf = weakSelf;
889918 if (!strongSelf) {
890919 return ;
891920 }
892921 strongSelf->_webAuthenticationSession = nil ;
893922 if (callbackURL) {
894923 [strongSelf->_session resumeExternalUserAgentFlowWithURL: callbackURL];
895- } else {
896- NSError *safariError =
897- [OIDErrorUtilities errorWithCode: OIDErrorCodeUserCanceledAuthorizationFlow
898- underlyingError: error
899- description: nil ];
900- [strongSelf->_session failExternalUserAgentFlowWithError: safariError];
924+ return ;
901925 }
902- }];
926+ BOOL isUserCancel = [error.domain isEqualToString: ASWebAuthenticationSessionErrorDomain] &&
927+ error.code == ASWebAuthenticationSessionErrorCodeCanceledLogin;
928+ if (useHTTPSCallback && !isUserCancel && [strongSelf startLegacyFallbackSession ]) {
929+ // Missing/unvalidated webcredentials association — legacy session took over
930+ return ;
931+ }
932+ NSError *safariError =
933+ [OIDErrorUtilities errorWithCode: OIDErrorCodeUserCanceledAuthorizationFlow
934+ underlyingError: error
935+ description: nil ];
936+ [strongSelf->_session failExternalUserAgentFlowWithError: safariError];
937+ };
903938
904- webAuthenticationSession.presentationContextProvider = self;
905- webAuthenticationSession.prefersEphemeralWebBrowserSession = _prefersEphemeralSession;
906- _webAuthenticationSession = webAuthenticationSession;
907- return [webAuthenticationSession start ];
939+ ASWebAuthenticationSession *session;
940+ if (useHTTPSCallback) {
941+ ASWebAuthenticationSessionCallback *callback =
942+ [ASWebAuthenticationSessionCallback callbackWithHTTPSHost: _host path: _path];
943+ session = [[ASWebAuthenticationSession alloc ] initWithURL: _requestURL
944+ callback: callback
945+ completionHandler: completionHandler];
946+ } else {
947+ // Matches AppAuth's OIDExternalUserAgentIOS default for https redirect URIs
948+ session = [[ASWebAuthenticationSession alloc ] initWithURL: _requestURL
949+ callbackURLScheme: @" https"
950+ completionHandler: completionHandler];
951+ }
952+ session.presentationContextProvider = self;
953+ session.prefersEphemeralWebBrowserSession = _prefersEphemeralSession;
954+ return session;
908955}
909956
910957- (void )dismissExternalUserAgentAnimated : (BOOL )animated completion : (nonnull void (^)(void ))completion {
0 commit comments