Skip to content

feat: allow enabling EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER on the Custom Tab intent - #1065

Open
jishidaaaaa wants to merge 3 commits into
auth0:mainfrom
jishidaaaaa:feature/custom-tabs-send-to-external-default-handler
Open

jishidaaaaa wants to merge 3 commits into
auth0:mainfrom
jishidaaaaa:feature/custom-tabs-send-to-external-default-handler

Conversation

@jishidaaaaa

@jishidaaaaa jishidaaaaa commented Sep 2, 2026 •

Copy link
Copy Markdown

Changes

When WebAuthProvider opens Universal Login in a Custom Tab, the Custom Tabs intent always targets the browser package explicitly (a Custom Tabs session implies it). Chrome interprets an intent that explicitly targets the browser as "the app wants to stay in the browser" and refuses to hand off redirects that happen in the initial navigation chain to another app, unless it trusts the calling app.

Until Chrome 120 (December 2023) that trust was only granted to Google-signed apps. On Chrome <= 119, when /authorize immediately 302-redirects to an https callback URL (Android App Links) — for example because the user already has an Auth0 session, or prompt=none is used — Chrome loads the callback URL inside the Custom Tab instead of launching the app. The tenant answers 404 Not found., which is what the user sees. Flows that involve user input (typing credentials) are not affected, because the input starts a new navigation chain with a user gesture.

Chrome has provided an opt-in escape hatch for exactly this case since Chrome 84: CustomTabsIntent.EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER. The androidx.browser javadoc describes it as: "A Custom Tab Intent from a Custom Tab session will always have the package set, so the Intent will always be to the browser. This extra can be used to allow the initial Intent navigation chain to leave the browser." CustomTabsOptions currently offers no way to set it.

This PR adds:

  • CustomTabsOptions.Builder#withSendToExternalDefaultHandlerEnabled(boolean) (public API, default false, opt-in).
  • When enabled, CustomTabsOptions#toIntent calls CustomTabsIntent.Builder#setSendToExternalDefaultHandlerEnabled(true).
  • Parcelable and toBuilder() round-trip support for the new flag.

Behavior is unchanged unless the option is enabled. The Auth Tab and TWA paths are not touched (Auth Tab handles the redirect itself; TWA is out of scope). The androidx.browser API used is available in the version already declared by the library (1.10.0).

Usage:

val ctOptions = CustomTabsOptions.newBuilder()
    .withSendToExternalDefaultHandlerEnabled(true)
    .build()

WebAuthProvider.login(account)
    .withCustomTabsOptions(ctOptions)
    .start(this, callback)

References

Testing

Unit tests added to CustomTabsOptionsTest:

  • the extra is present when the option is enabled, and survives a Parcel round-trip
  • the extra is absent by default
  • the flag is preserved through toBuilder() (copyWithEphemeralBrowsing())

I could not run the Gradle test suite locally (no Android SDK on the machine used for this change), so I am relying on this repository's CI for the unit tests and lint. The androidx.browser APIs used (Builder#setSendToExternalDefaultHandlerEnabled, CustomTabsIntent#isSendToExternalDefaultHandlerEnabled, EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER) were verified to exist in the browser:1.10.0 artifact the library already depends on.

How to reproduce the underlying problem and verify the fix on a device:

  1. App with auth0Scheme = "https" and verified Android App Links, on a device running Chrome <= 119.
  2. Make sure an Auth0 session exists in Chrome (e.g. log in once on the web in Chrome on the same device), or use prompt=none.
  3. Start WebAuthProvider.login(...). Without the option, /authorize redirects straight to the callback URL and the Custom Tab shows Not found.. With withSendToExternalDefaultHandlerEnabled(true), the app receives the callback.
  4. On Chrome 120+ both variants behave the same (the app receives the callback).
  • This change adds unit test coverage
  • This change adds integration test coverage
  • This change has been tested on the latest version of the platform/language or why not — see above: unit tests are expected to run in CI; device verification of the Chrome <= 119 scenario is still pending on my side.

Checklist

Summary by CodeRabbit

  • New Features

    • Added an optional Custom Tabs setting that lets the initial navigation chain hand off to the device’s default handler, such as when an authorization redirect opens an app link.
    • The setting is disabled by default and is available through the Custom Tabs configuration builder. It is preserved when configurations are copied or restored.
    • This option may be relevant to Android App Links on Chrome 119 and older; Chrome 120 and newer handles this automatically, and Auth Tab is unaffected.
  • Documentation

    • Added Kotlin and Java examples showing how to enable the setting.

…stom Tab intent

Adds CustomTabsOptions.Builder#withSendToExternalDefaultHandlerEnabled(boolean).
When enabled, toIntent() sets CustomTabsIntent.EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER
so that the initial navigation chain of the Custom Tab may leave the browser and
launch the app. This lets https (App Links) callbacks reach the app on Chrome
versions prior to 120 when /authorize redirects straight to the callback URL
without user interaction (existing session, prompt=none). Disabled by default.
@jishidaaaaa
jishidaaaaa requested a review from a team as a code owner September 2, 2026 06:48
@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 3200c2a7-1576-4eec-b7e8-e5141a9fa51f

📥 Commits

Reviewing files that changed from the base of the PR and between 8057d6d and 401a886.

📒 Files selected for processing (2)
  • auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java
  • examples/custom-tabs-ui.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

CustomTabsOptions adds a configurable external default handler option. The setting propagates through builders, copied options, and Parcel serialization. When enabled, it sets the corresponding flag on generated Custom Tab intents.

Changes

External handler option

Layer / File(s) Summary
Option state and propagation
auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java
Adds the option to the builder and CustomTabsOptions. The option is copied by toBuilder() and preserved through Parcel serialization.
Intent behavior and validation
auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java, auth0/src/test/java/com/auth0/android/provider/CustomTabsOptionsTest.java, examples/custom-tabs-ui.md
Sets the Custom Tab external handler flag when enabled. Tests cover enabled, default, copied, and Parcel round-trip behavior. Documentation describes the redirect case and includes Kotlin and Java examples.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Feature

Suggested reviewers: amitsingh05667

Merge Risk: ⚪ Minimal · up to 401a8

The option is opt-in and preserves existing default behavior. No actionable merge-blocking risk is established; normal build and test checks remain appropriate.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 401a8

The behavior is disabled by default, and authentication responses still pass through state, PKCE, and ID-token validation. Enabling it changes who may receive an initial redirect, so callback ownership depends on browser and Android link resolution. No authentication-control bypass was established, but behavior with failed link verification remains unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed exposure is the initial navigation chain of browser flows whose caller enables this option, including logout. Its recipient boundary is the device's external-handler routing; the SDK does not constrain this extra to a particular callback component.

Trust Boundaries and Controls

  • observed — The manifest requests App Link verification and isolates external callback entry from the non-exported authentication activity. State, PKCE, and token validation remain controls against accepting attacker-supplied callbacks. The manifest declaration does not prove successful device-level verification or intended-handler selection.

Resilience and Maintainability Implications

  • observed — The handoff continues through existing activity completion, duplicate-delivery guards, accepted-result manager reset, and service-unbinding paths. It introduces no separate SDK completion path. Existing concurrency and interruption limitations are not resolved by this change.

Hardening Proposals

  • proposed — Document verified callback ownership as an adoption prerequisite and validate initial-chain routing with failed verification and competing handlers on supported browser/device combinations. This would resolve recipient-selection uncertainty rather than remediate a demonstrated authentication bypass.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 2 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding opt-in support for EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER on Custom Tab intents.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 2 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@pmathew92

Copy link
Copy Markdown
Member

Hi @jishidaaaaa , thanks for raising this PR. We will review this

This branch has not been deployed

No deployments
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.

3 participants