Skip to content

feat(ios): natively intercept https redirect URIs via ASWebAuthenticationSession callback (iOS 17.4+)#1122

Open
danchily2 wants to merge 6 commits into
FormidableLabs:mainfrom
danchily2:feat/ios-https-callback-aswebauthsession
Open

feat(ios): natively intercept https redirect URIs via ASWebAuthenticationSession callback (iOS 17.4+)#1122
danchily2 wants to merge 6 commits into
FormidableLabs:mainfrom
danchily2:feat/ios-https-callback-aswebauthsession

Conversation

@danchily2

@danchily2 danchily2 commented Jun 5, 2026

Copy link
Copy Markdown

Fixes #987

Description

When the redirect URI is an https universal link, OIDExternalUserAgentIOS creates the session with callbackURLScheme:@"https" — a scheme ASWebAuthenticationSession does not support — so the session never intercepts the redirect. Flow completion then depends on the universal link opening the app from inside the auth session, which is not triggered by server-side redirects or JS navigation, and is sporadically dropped entirely. The result is an authorize() promise that never settles, with the user stuck on a permanently disabled login UI (also reported as #932; root cause tracked upstream since 2018 in openid/AppAuth-iOS#367, and the callbackURLScheme: initializer is deprecated per openid/AppAuth-iOS#847). The same failure class affects other SDKs (e.g. google/GoogleSignIn-iOS#388). The common workaround — a hosted "trampoline" page forwarding the callback to a custom scheme — reintroduces the custom scheme universal links were adopted to avoid, and the in-session JS hop is itself sporadically blocked by WebKit.

iOS 17.4 added ASWebAuthenticationSessionCallback callbackWithHTTPSHost:path:, which lets the session intercept an https redirect natively — no app-site-association timing dependency, no trampoline page, no custom scheme.

This PR adds RNAppAuthHTTPSExternalUserAgent (modeled on AppAuth's OIDExternalUserAgentIOS, including ephemeral-session and presentation-context handling) and uses it automatically when:

  • no iosCustomBrowser is requested, and
  • the redirect URI scheme is https, and
  • running on iOS 17.4+.

Everything else — custom browsers, custom-scheme redirect URIs, iOS < 17.4 — keeps the existing behavior, so this is a progressive enhancement with no API surface change.

Notes per CONTRIBUTING:

  • iOS-only by design: Android already supports https App Links redirects natively through AppAuth-Android's RedirectUriReceiverActivity intent filters, so there is no equivalent defect to replicate there.
  • No JS/TypeScript surface change → no index.js/index.spec.js/typings/readme updates needed; yarn test and yarn lint are unaffected (native-only change).
  • Changeset included (minor).

Steps to verify

Requirement: the https callback path needs the callback host registered as an associated domain with the webcredentials service type — both the com.apple.developer.associated-domains entitlement (webcredentials:example.com) and a webcredentials entry for the app in the domain's apple-app-site-association file. Without the association, the agent transparently falls back to the legacy callbackURLScheme session (AppAuth's default behavior), so apps without the association are unaffected.

  1. Configure an OIDC provider with an https universal-link redirect URI, add the webcredentials associated domain (entitlement + AASA), and run on an iOS 17.4+ device or simulator.
  2. Call authorize() and complete sign-in: the session intercepts the redirect itself — the sheet closes on the provider's 302 without loading the redirect page, and authorize() resolves with the token response.
  3. Cancel the sheet: authorize() rejects with the user-cancelled error as before.
  4. Remove the webcredentials association and repeat: the fallback path engages and behavior matches current main.
  5. Run on iOS < 17.4 (or with iosCustomBrowser, or a custom-scheme redirect URI): behavior is unchanged from current main.

🤖 Generated with Claude Code

…tionSession callback (iOS 17.4+)

With an https (universal link) redirect URI, the session is created with
callbackURLScheme:@"https", which ASWebAuthenticationSession does not
support, so it never intercepts the redirect. Completion then depends on
the universal link opening the app from inside the auth session - which
is not triggered by server redirects or JS navigation and sporadically
never happens, leaving authorize() pending forever with the user stuck
on a disabled login UI (FormidableLabs#987, FormidableLabs#932; see also openid/AppAuth-iOS#367).

On iOS 17.4+ Apple provides ASWebAuthenticationSessionCallback
callbackWithHTTPSHost:path:, letting the session intercept the https
redirect natively - no app-site-association dependency, no trampoline
pages, no custom scheme. This adds an external user agent built on that
API and uses it automatically when no iosCustomBrowser is requested and
the redirect URI scheme is https. Behavior on iOS < 17.4 is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

@danchily2 is attempting to deploy a commit to the formidable-labs Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 44201c0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
react-native-app-auth Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

danchily2 and others added 5 commits June 5, 2026 11:23
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
… is missing

The https callback requires the callback host to be an associated domain
with the webcredentials service type. Without it the session refuses to
start (start returns NO, or the completion handler fires immediately with
a non-cancel error), which would hard-fail every sign-in. Detect both
cases and transparently fall back to the legacy callbackURLScheme session,
preserving AppAuth's default behavior for apps without the association.

Co-Authored-By: Claude <noreply@anthropic.com>
…ror code

The missing-association failure is reported with the SAME error code as a
user cancellation (ASWebAuthenticationSessionErrorCodeCanceledLogin), so
the previous fallback check never engaged and every sign-in hard-failed
('Application ... is not associated with domain ... Using HTTPS callbacks
requires Associated Domains using the webcredentials service type').
The association failure carries an NSLocalizedFailureReason while genuine
user cancellations do not - use that to trigger the legacy-session
fallback. Verified on an iOS 26 simulator: with the association missing,
the fallback engages and sign-in completes; with a newer login attempt
started, stale resolutions are still discarded.

Co-Authored-By: Claude <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an iOS-only progressive enhancement to ensure authorize() can complete when the redirect URI is an https universal link on iOS 17.4+, by using the new ASWebAuthenticationSession HTTPS callback API so the auth session intercepts the redirect natively.

Changes:

  • Introduces RNAppAuthHTTPSExternalUserAgent, modeled after AppAuth-iOS’s iOS external user agent, using ASWebAuthenticationSessionCallback callbackWithHTTPSHost:path: on iOS 17.4+.
  • Automatically selects the new external user agent for https redirect URIs on iOS 17.4+ when iosCustomBrowser is not specified, with a legacy fallback path.
  • Adds a Changesets entry to publish the enhancement as a minor change.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
packages/react-native-app-auth/ios/RNAppAuth.m Adds an iOS 17.4+ external user agent to intercept https redirect URIs inside ASWebAuthenticationSession, plus selection logic in authorize().
.changeset/https-callback-ios.md Declares a minor release describing the iOS 17.4+ HTTPS-callback enhancement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to +35
API_AVAILABLE(ios(17.4))
@interface RNAppAuthHTTPSExternalUserAgent : NSObject <OIDExternalUserAgent, ASWebAuthenticationPresentationContextProviding>

- (nonnull instancetype)initWithPresentingViewController:(nonnull UIViewController *)presentingViewController
prefersEphemeralSession:(BOOL)prefersEphemeralSession
host:(nonnull NSString *)host
path:(nonnull NSString *)path;

@end
Comment on lines +409 to +422
// Prefer the native https-callback session for https (universal link) redirect URIs
// (see RNAppAuthHTTPSExternalUserAgent above). Falls back to default behavior pre-17.4.
if (externalUserAgent == nil) {
if (@available(iOS 17.4, *)) {
NSURL *httpsRedirectURL = [NSURL URLWithString:redirectUrl];
if ([httpsRedirectURL.scheme isEqualToString:@"https"] && httpsRedirectURL.host != nil) {
externalUserAgent = [[RNAppAuthHTTPSExternalUserAgent alloc]
initWithPresentingViewController:presentingViewController
prefersEphemeralSession:prefersEphemeralSession
host:httpsRedirectURL.host
path:httpsRedirectURL.path.length > 0 ? httpsRedirectURL.path : @"/"];
}
}
}
Comment on lines +847 to +852
/**
* Implementation modeled on AppAuth's OIDExternalUserAgentIOS, but built
* with [ASWebAuthenticationSessionCallback callbackWithHTTPSHost:path:] so the session
* intercepts the https redirect itself (iOS 17.4+).
*/
@implementation RNAppAuthHTTPSExternalUserAgent {
return _presentingViewController.view.window;
}

@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] Universal link not working as redirect URI

2 participants