Skip to content

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

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

feat: allow enabling EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER on the Custom Tab intent#1065
jishidaaaaa wants to merge 1 commit 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 option to allow Custom Tab navigation to open the device’s default handler when enabled.
    • The setting is available through the Custom Tabs configuration builder and remains preserved when configurations are copied or restored.
    • The option is disabled by default, maintaining existing navigation behavior unless explicitly enabled.
  • Bug Fixes

    • Improved handling of authorization flows that redirect directly to an app callback URL on supported browser versions.

…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 Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 26ea9e37-3bd2-41cd-99c6-a59877c4af13

📥 Commits

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

📒 Files selected for processing (2)
  • auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java
  • auth0/src/test/java/com/auth0/android/provider/CustomTabsOptionsTest.java

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

CustomTabsOptions adds a configurable external default handler flag. The setting propagates through builders, copied options, Parcel serialization, and generated intents. Tests verify enabled, default, copied, and serialized behavior.

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, exposes a public builder method, preserves it through toBuilder(), and serializes it through Parcel operations.
Intent behavior and validation
auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java, auth0/src/test/java/com/auth0/android/provider/CustomTabsOptionsTest.java
Sets the Custom Tab external handler flag when enabled. Tests cover enabled, default, copied, and Parcel round-trip behavior.

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

Merge Risk: 🔵 Low · up to 8057d

The opt-in can route HTTPS authentication callbacks to an unintended or unavailable app if verified App Links are not correctly configured, while existing validation limits unauthorized session or token use and default behavior remains unchanged. The PR is mergeable with explicit owner awareness to document the verified-link requirement and confirm compatibility expectations for serialized options.

Suggested reviewers: amitsingh05667

Sequence Diagram(s)

sequenceDiagram
  participant Builder
  participant CustomTabsOptions
  participant CustomTabsIntent
  Builder->>CustomTabsOptions: build with external handler enabled
  CustomTabsOptions->>CustomTabsIntent: set external handler flag
  CustomTabsIntent-->>CustomTabsOptions: report enabled flag
Loading
🚥 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. 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 the Custom Tab intent.
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@pmathew92

Copy link
Copy Markdown
Contributor

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

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.

2 participants