Skip to content

Support SSO flow in browser plugin #2485

@sandstrom

Description

@sandstrom

Feature Request

Plugin

@capacitor/browser

Description

The Browser plugin opens URLs using SFSafariViewController on iOS and Chrome Custom Tabs on Android. The plugin's own README notes that SFSafariViewController is "compliant with leading OAuth service in-app-browser requirements," and this is broadly true — both primitives do share cookies and session data with the system browser, which satisfies many IdP requirements.

However, they are still not the right tool for structured OAuth/OIDC callback flows, for a different reason: neither primitive provides a way to intercept and return the redirect URI back to the calling app as a resolved value. When an IdP redirects to a custom URL scheme (e.g. myapp://auth/callback?code=...) or a universal link at the end of an auth flow, SFSafariViewController has no mechanism to catch that redirect and return it to the app.

The result is that the browser either stalls on a blank page or the appUrlOpen event fires inconsistently, depending on how the redirect URI is configured. There is no clean, reliable way to close the browser and return the authorization code or token to the calling code as a promise resolution.

That said, it's not impossible to make SSO work in Capacitor, but the proper platform APIs aren't being exposed.

Both platforms provide dedicated APIs designed specifically for this use case, and it would be valuable to expose them through the Browser plugin or as a new dedicated method.

Platform(s)

  • iOS
  • Android

Preferred Solution

Expose platform authentication session APIs, either as a new method on the existing Browser plugin or as a separate openAuthSession surface:

iOS: ASWebAuthenticationSession

This is the current recommended API (available since iOS 12), replacing the deprecated SFAuthenticationSession. It is purpose-built for OAuth and SSO flows: it opens a browser context, monitors for a redirect to the specified callback URL scheme, and returns the full callback URL to the app as a completion handler.

It presents an explicit consent prompt to the user before sharing any cookies with the identity provider session, and supports an prefersEphemeralWebBrowserSession option to use a private, non-shared session instead (which suppresses the consent prompt and is appropriate for flows that don't need SSO cookie persistence).

Android: Chrome Custom Tabs + redirect intent handling

Android does not have a direct 1:1 equivalent to ASWebAuthenticationSession. The recommended approach is the existing Custom Tabs implementation, but augmented with proper deep link / intent filter handling so the app can receive the OAuth redirect URI when the IdP redirects back. This requires the app to register an intent filter for the callback scheme and the plugin to wire up the round-trip correctly. The AppAuth for Android library documents this pattern well and would be a useful reference implementation.

A minimal API addition could look like:

const result = await Browser.openAuthSession({
  url: 'https://idp.example.com/authorize?...',
  callbackURLScheme: 'myapp',
  // iOS only: omit to default to shared session with consent prompt
  prefersEphemeralSession: true,
});

// result.callbackURL contains the full redirect URI the IdP sent back
// e.g. "myapp://auth/callback?code=abc123&state=xyz"

On iOS this maps directly to ASWebAuthenticationSession. On Android it would open a Custom Tab and resolve the promise when the app receives the matching intent via the registered deep link.

Alternatives

  • The @capacitor/community ecosystem has capacitor-app-auth, but it is not officially maintained and tends to lag behind platform API changes.
  • Manually wiring up App.addListener('appUrlOpen', ...) alongside Browser.open() is a workaround, but it is unreliable — the browser does not close itself when the redirect fires, and sequencing the listener with the browser open/close lifecycle is fragile and error-prone (see Capacitor Browser - Universal link callback not triggered during auth flow  #628).

Additional Context

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions