diff --git a/EXAMPLES.md b/EXAMPLES.md index 0c3d403c..950da8b3 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -340,6 +340,10 @@ WebAuthProvider.login(account) ``` + +> [!NOTE] +> Ephemeral browsing is not supported with Trusted Web Activity. When `withTrustedWebActivity()` is used, `withEphemeralBrowsing()` has no effect and the flow launches as a Trusted Web Activity using the browser's normal (non-isolated) session. This is a platform limitation — a TWA shares the user's browser profile by design, so it cannot run in an isolated ephemeral session. + ## Auth Tab @@ -402,7 +406,7 @@ When `withAuthTab()` is combined with `withCustomTabsOptions()`, only a subset o |---|---| | `withToolbarColor()` | ✅ Applied to the Auth Tab toolbar | | `showTitle()` | ❌ Ignored — Auth Tab has no title-visibility option | -| `withEphemeralBrowsing()` | ❌ Ignored — Auth Tab does not support ephemeral sessions. Use a regular Custom Tab if session isolation is required | +| `withEphemeralBrowsing()` | ✅ Honored — the Auth Tab runs in an isolated ephemeral session when the browser supports it (requires Chrome 136+ or a compatible browser); otherwise a warning is logged and it falls back to a regular Auth Tab | | `withInitialHeight()` / `withInitialWidth()` | ❌ Ignored — Auth Tab is always full-screen | | `withToolbarCornerRadius()` | ❌ Ignored | | `withSideSheetBreakpoint()` | ❌ Ignored | diff --git a/auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java b/auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java index 6002ff22..a9c4a8da 100644 --- a/auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java +++ b/auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java @@ -197,6 +197,17 @@ Intent toIntent(@NonNull Context context, @Nullable CustomTabsSession session) { @SuppressLint("ResourceType") AuthTabIntent.Builder toAuthTabIntentBuilder(@NonNull Context context) { AuthTabIntent.Builder builder = new AuthTabIntent.Builder(); + if (ephemeralBrowsing) { + String preferredPackage = this.getPreferredPackage(context.getPackageManager()); + if (preferredPackage != null + && CustomTabsClient.isEphemeralBrowsingSupported(context, preferredPackage)) { + builder.setEphemeralBrowsingEnabled(true); + } else { + Log.w(TAG, "Ephemeral browsing was requested but is not supported by the " + + "current browser (" + preferredPackage + "). " + + "Falling back to a regular Auth Tab."); + } + } if (toolbarColor > 0) { final AuthTabColorSchemeParams params = new AuthTabColorSchemeParams.Builder() .setToolbarColor(ContextCompat.getColor(context, toolbarColor)) diff --git a/auth0/src/test/java/com/auth0/android/provider/CustomTabsOptionsTest.java b/auth0/src/test/java/com/auth0/android/provider/CustomTabsOptionsTest.java index aa2808f1..d77fba74 100644 --- a/auth0/src/test/java/com/auth0/android/provider/CustomTabsOptionsTest.java +++ b/auth0/src/test/java/com/auth0/android/provider/CustomTabsOptionsTest.java @@ -5,6 +5,7 @@ import android.content.pm.PackageManager; import android.os.Parcel; +import androidx.browser.auth.AuthTabIntent; import androidx.browser.customtabs.CustomTabsClient; import androidx.browser.customtabs.CustomTabsIntent; import androidx.core.content.ContextCompat; @@ -400,6 +401,86 @@ public void shouldIgnoreEphemeralBrowsingWhenDisabledCustomTabBrowser() { assertThat(hasLogWithMessage("Ephemeral browsing was requested"), is(false)); } + @Test + public void shouldSetEphemeralBrowsingOnAuthTabWhenSupported() { + Activity activity = spy(Robolectric.setupActivity(Activity.class)); + BrowserPickerTest.setupBrowserContext(activity, Collections.singletonList("com.android.chrome"), null, null); + + customTabsClientMock = Mockito.mockStatic(CustomTabsClient.class); + customTabsClientMock.when(() -> + CustomTabsClient.isEphemeralBrowsingSupported(any(), eq("com.android.chrome")) + ).thenReturn(true); + + BrowserPicker browserPicker = BrowserPicker.newBuilder().build(); + CustomTabsOptions options = CustomTabsOptions.newBuilder() + .withBrowserPicker(browserPicker) + .withEphemeralBrowsing() + .withAuthTab() + .build(); + assertThat(options, is(notNullValue())); + + AuthTabIntent authTabIntent = options.toAuthTabIntentBuilder(activity).build(); + assertThat(authTabIntent, is(notNullValue())); + + // Verify ephemeral browsing extra is set on the Auth Tab intent + assertThat(authTabIntent.intent.getBooleanExtra(CustomTabsIntent.EXTRA_ENABLE_EPHEMERAL_BROWSING, false), is(true)); + + // Verify isEphemeralBrowsingSupported was called + customTabsClientMock.verify(() -> + CustomTabsClient.isEphemeralBrowsingSupported(any(), eq("com.android.chrome")) + ); + + // Verify no warning was logged + assertThat(hasLogWithMessage("Ephemeral browsing was requested"), is(false)); + } + + @Test + public void shouldFallbackWithWarningWhenEphemeralNotSupportedOnAuthTab() { + Activity activity = spy(Robolectric.setupActivity(Activity.class)); + BrowserPickerTest.setupBrowserContext(activity, Collections.singletonList("com.android.chrome"), null, null); + + customTabsClientMock = Mockito.mockStatic(CustomTabsClient.class); + customTabsClientMock.when(() -> + CustomTabsClient.isEphemeralBrowsingSupported(any(), eq("com.android.chrome")) + ).thenReturn(false); + + BrowserPicker browserPicker = BrowserPicker.newBuilder().build(); + CustomTabsOptions options = CustomTabsOptions.newBuilder() + .withBrowserPicker(browserPicker) + .withEphemeralBrowsing() + .withAuthTab() + .build(); + + AuthTabIntent authTabIntent = options.toAuthTabIntentBuilder(activity).build(); + assertThat(authTabIntent, is(notNullValue())); + + assertThat(hasLogWithMessage("Ephemeral browsing was requested but is not supported"), is(true)); + + // Verify ephemeral browsing extra is not set (fallback to a regular Auth Tab) + assertThat(authTabIntent.intent.getBooleanExtra(CustomTabsIntent.EXTRA_ENABLE_EPHEMERAL_BROWSING, false), is(false)); + } + + @Test + public void shouldNotSetEphemeralBrowsingOnAuthTabByDefault() { + customTabsClientMock = Mockito.mockStatic(CustomTabsClient.class); + + CustomTabsOptions options = CustomTabsOptions.newBuilder() + .withAuthTab() + .build(); + assertThat(options, is(notNullValue())); + + AuthTabIntent authTabIntent = options.toAuthTabIntentBuilder(context).build(); + assertThat(authTabIntent, is(notNullValue())); + + // Ephemeral support should not be queried when ephemeral browsing was not requested + customTabsClientMock.verifyNoInteractions(); + + // Verify ephemeral browsing extra is not set + assertThat(authTabIntent.intent.getBooleanExtra(CustomTabsIntent.EXTRA_ENABLE_EPHEMERAL_BROWSING, false), is(false)); + + assertThat(hasLogWithMessage("Ephemeral browsing was requested"), is(false)); + } + // --- Partial Custom Tabs: Bottom Sheet --- // Note: Robolectric uses density=1.0 (mdpi) by default, so dp values equal px values in tests.