Skip to content

Android: minimizing the Chrome Custom Tab rejects authorize() with USER_CANCELLED while the browser stays alive (zombie session) #1584

Description

@mGallee

Checklist

Description

On Android, when authorize() (useAuth0) opens the login in a Chrome Custom Tab and the user minimizes the tab using the toolbar minimize button (the "Minimized Custom Tabs" / picture-in-picture feature, enabled by default since Chrome 122), the app returns to the foreground and the authorize() promise immediately rejects with a WebAuthError of code USER_CANCELLED (a0.session.user_cancelled).

The problem is that the Custom Tab is not closed — it stays alive in a floating/minimized window (a "zombie" browser). The user can re-open it and complete the Auth0 login in the browser, but the SDK has already rejected the promise and abandoned the transaction, so the eventual redirect is dropped and the credentials never reach the app. The result is an inconsistent state: the app believes the user cancelled and is unauthenticated, while the browser shows a successful (or completable) login.

Compounding this, cancelWebAuth() is a no-op on Android (the native A0Auth0Module implementation just resolves), so the app has no way to programmatically dismiss the zombie tab after the rejection either.

Expected behavior (any one of these would resolve it):

  • Minimizing/backgrounding the Custom Tab should not be treated as a hard USER_CANCELLED that abandons the transaction; the flow should be resumable when the user returns and completes login, or
  • When the flow is cancelled, the Custom Tab should be dismissed/closed so it can't be resumed into a dead-end, or
  • cancelWebAuth() should be implemented on Android so the app can dismiss the tab and reconcile state, or
  • Provide a way to opt out of the minimize button (e.g. via Auth Tab / AuthTabIntent) for the web-auth flow.

Reproduction

Consistently reproducible on Android (Chrome ≥ 122 as the Custom Tabs provider):

  1. In a bare React Native app, call const { authorize } = useAuth0() and invoke await authorize({ ... }).
  2. The Chrome Custom Tab opens with the Auth0 login page.
  3. Tap the minimize (down-arrow / PiP) button in the Custom Tab toolbar (or press Home and reopen the app).
  4. The app foregrounds and the authorize() promise rejects with WebAuthError USER_CANCELLED (a0.session.user_cancelled).
  5. Observe that the Custom Tab is still alive (minimized PiP window / in recents). Re-open it and finish the login — the browser completes, but the app remains unauthenticated and the credentials are never delivered.

Additional context

  • Native cause: the Android module rejects with "a0.session.user_cancelled" in the error.isCanceled branch of the web-auth callback (android/.../A0Auth0Module.kt). The login uses the standard com.auth0.android.provider.WebAuthProvider Custom Tabs flow.
  • cancelWebAuth() / resumeWebAuth() are dummy implementations on Android in A0Auth0Module.kt ("only needed in iOS"), so the JS layer can't dismiss the tab after cancellation.
  • The minimize button is Chrome's Minimized Custom Tabs feature (on by default since Chrome 122); there is no public CustomTabsIntent API to hide just that button. Chrome's Auth Tab (AuthTabIntent, AndroidX Browser ≥ ~1.8) removes it, but the SDK still bundles androidx.browser:browser:1.2.0 and uses WebAuthProvider.
  • Related Chromium/embedder context: minimize PiP keeps the Custom Tab task alive while the host app resumes, which is what triggers the resume-without-redirect cancellation.
  • iOS is unaffected (uses ASWebAuthenticationSession; cancelWebAuth() works there).

react-native-auth0 version

5.7.0 (also reproduces on 5.4.0)

React Native version

0.84.1

Expo version

N/A (bare React Native)

Platform

Android

Platform version(s)

Android 16 (API 36), Samsung SM-A156B (Galaxy A15 5G). Custom Tabs provider: Chrome 149.0.7827.197

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis points to a verified bug in the code

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions