Skip to content

Commit ec444ff

Browse files
authored
feat: Add support for ephemeral session for chrome custom tabs (#916)
1 parent 7da0c8e commit ec444ff

File tree

5 files changed

+321
-7
lines changed

5 files changed

+321
-7
lines changed

EXAMPLES.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [Changing the Return To URL scheme](#changing-the-return-to-url-scheme)
1212
- [Specify a Custom Logout URL](#specify-a-custom-logout-url)
1313
- [Trusted Web Activity](#trusted-web-activity)
14+
- [Ephemeral Browsing [Experimental]](#ephemeral-browsing-experimental)
1415
- [DPoP [EA]](#dpop-ea)
1516
- [Authentication API](#authentication-api)
1617
- [Login with database connection](#login-with-database-connection)
@@ -228,6 +229,42 @@ WebAuthProvider.login(account)
228229
.await(this)
229230
```
230231

232+
## Ephemeral Browsing [Experimental]
233+
234+
> **WARNING**
235+
> Ephemeral browsing support in Auth0.Android is still experimental and can change in the future. Please test it thoroughly in all the targeted browsers
236+
> and OS variants and let us know your feedback.
237+
238+
Ephemeral browsing launches the Chrome Custom Tab in a fully isolated session — cookies, cache, history, and credentials are deleted when the tab closes. This is equivalent to incognito/private mode for Custom Tabs, useful for privacy-focused authentication flows.
239+
240+
Requires Chrome 136+ or a compatible browser. On unsupported browsers, the SDK falls back to a regular Custom Tab and logs a warning.
241+
242+
```kotlin
243+
WebAuthProvider.login(account)
244+
.withEphemeralBrowsing()
245+
.start(this, callback)
246+
```
247+
248+
<details>
249+
<summary>Using async/await</summary>
250+
251+
```kotlin
252+
WebAuthProvider.login(account)
253+
.withEphemeralBrowsing()
254+
.await(this)
255+
```
256+
</details>
257+
258+
<details>
259+
<summary>Using Java</summary>
260+
261+
```java
262+
WebAuthProvider.login(account)
263+
.withEphemeralBrowsing()
264+
.start(this, callback);
265+
```
266+
</details>
267+
231268
## DPoP [EA]
232269

233270
> [!NOTE]

auth0/src/main/java/com/auth0/android/provider/CustomTabsOptions.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@
77
import android.net.Uri;
88
import android.os.Parcel;
99
import android.os.Parcelable;
10+
import android.util.Log;
1011

1112
import androidx.annotation.ColorRes;
1213
import androidx.annotation.NonNull;
1314
import androidx.annotation.Nullable;
1415
import androidx.browser.customtabs.CustomTabColorSchemeParams;
16+
import androidx.browser.customtabs.CustomTabsClient;
1517
import androidx.browser.customtabs.CustomTabsIntent;
1618
import androidx.browser.customtabs.CustomTabsSession;
1719
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
1820
import androidx.core.content.ContextCompat;
1921

22+
import com.auth0.android.annotation.ExperimentalAuth0Api;
2023
import com.auth0.android.authentication.AuthenticationException;
2124

2225
import java.util.List;
@@ -26,6 +29,8 @@
2629
*/
2730
public class CustomTabsOptions implements Parcelable {
2831

32+
private static final String TAG = "CustomTabsOptions";
33+
2934
private final boolean showTitle;
3035
@ColorRes
3136
private final int toolbarColor;
@@ -34,11 +39,14 @@ public class CustomTabsOptions implements Parcelable {
3439
@Nullable
3540
private final List<String> disabledCustomTabsPackages;
3641

37-
private CustomTabsOptions(boolean showTitle, @ColorRes int toolbarColor, @NonNull BrowserPicker browserPicker, @Nullable List<String> disabledCustomTabsPackages) {
42+
private final boolean ephemeralBrowsing;
43+
44+
private CustomTabsOptions(boolean showTitle, @ColorRes int toolbarColor, @NonNull BrowserPicker browserPicker, @Nullable List<String> disabledCustomTabsPackages, boolean ephemeralBrowsing) {
3845
this.showTitle = showTitle;
3946
this.toolbarColor = toolbarColor;
4047
this.browserPicker = browserPicker;
4148
this.disabledCustomTabsPackages = disabledCustomTabsPackages;
49+
this.ephemeralBrowsing = ephemeralBrowsing;
4250
}
4351

4452
@Nullable
@@ -60,6 +68,12 @@ boolean isDisabledCustomTabBrowser(@NonNull String preferredPackage) {
6068
return disabledCustomTabsPackages != null && disabledCustomTabsPackages.contains(preferredPackage);
6169
}
6270

71+
@NonNull
72+
CustomTabsOptions copyWithEphemeralBrowsing() {
73+
return new CustomTabsOptions(showTitle, toolbarColor, browserPicker,
74+
disabledCustomTabsPackages, true);
75+
}
76+
6377
/**
6478
* Create a new CustomTabsOptions.Builder instance.
6579
*
@@ -82,6 +96,18 @@ Intent toIntent(@NonNull Context context, @Nullable CustomTabsSession session) {
8296
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(session)
8397
.setShowTitle(showTitle)
8498
.setShareState(CustomTabsIntent.SHARE_STATE_OFF);
99+
100+
if (ephemeralBrowsing) {
101+
if (preferredPackage != null
102+
&& CustomTabsClient.isEphemeralBrowsingSupported(context, preferredPackage)) {
103+
builder.setEphemeralBrowsingEnabled(true);
104+
} else {
105+
Log.w(TAG, "Ephemeral browsing was requested but is not supported by the "
106+
+ "current browser (" + preferredPackage + "). "
107+
+ "Falling back to a regular Custom Tab.");
108+
}
109+
}
110+
85111
if (toolbarColor > 0) {
86112
//Resource exists
87113
final CustomTabColorSchemeParams.Builder colorBuilder = new CustomTabColorSchemeParams.Builder()
@@ -108,6 +134,7 @@ protected CustomTabsOptions(@NonNull Parcel in) {
108134
toolbarColor = in.readInt();
109135
browserPicker = in.readParcelable(BrowserPicker.class.getClassLoader());
110136
disabledCustomTabsPackages = in.createStringArrayList();
137+
ephemeralBrowsing = in.readByte() != 0;
111138
}
112139

113140
@Override
@@ -116,6 +143,7 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
116143
dest.writeInt(toolbarColor);
117144
dest.writeParcelable(browserPicker, flags);
118145
dest.writeStringList(disabledCustomTabsPackages);
146+
dest.writeByte((byte) (ephemeralBrowsing ? 1 : 0));
119147
}
120148

121149
@Override
@@ -147,11 +175,14 @@ public static class Builder {
147175
@Nullable
148176
private List<String> disabledCustomTabsPackages;
149177

178+
private boolean ephemeralBrowsing;
179+
150180
Builder() {
151181
this.showTitle = false;
152182
this.toolbarColor = 0;
153183
this.browserPicker = BrowserPicker.newBuilder().build();
154184
this.disabledCustomTabsPackages = null;
185+
this.ephemeralBrowsing = false;
155186
}
156187

157188
/**
@@ -212,14 +243,35 @@ public Builder withDisabledCustomTabsPackages(List<String> disabledCustomTabsPac
212243
return this;
213244
}
214245

246+
/**
247+
* Enable ephemeral browsing for the Custom Tab.
248+
* When enabled, the Custom Tab runs in an isolated session — cookies, cache,
249+
* history, and credentials are deleted when the tab closes.
250+
* Requires Chrome 136+ or a compatible browser. On unsupported browsers,
251+
* a warning is logged and a regular Custom Tab is used instead.
252+
* By default, ephemeral browsing is disabled.
253+
*
254+
* <p><b>Warning:</b> Ephemeral browsing support in Auth0.Android is still experimental
255+
* and can change in the future. Please test it thoroughly in all the targeted browsers
256+
* and OS variants and let us know your feedback.</p>
257+
*
258+
* @return this same builder instance.
259+
*/
260+
@ExperimentalAuth0Api
261+
@NonNull
262+
public Builder withEphemeralBrowsing() {
263+
this.ephemeralBrowsing = true;
264+
return this;
265+
}
266+
215267
/**
216268
* Create a new CustomTabsOptions instance with the customization settings.
217269
*
218270
* @return an instance of CustomTabsOptions with the customization settings.
219271
*/
220272
@NonNull
221273
public CustomTabsOptions build() {
222-
return new CustomTabsOptions(showTitle, toolbarColor, browserPicker, disabledCustomTabsPackages);
274+
return new CustomTabsOptions(showTitle, toolbarColor, browserPicker, disabledCustomTabsPackages, ephemeralBrowsing);
223275
}
224276
}
225277

auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.os.Bundle
77
import android.util.Log
88
import androidx.annotation.VisibleForTesting
99
import com.auth0.android.Auth0
10+
import com.auth0.android.annotation.ExperimentalAuth0Api
1011
import com.auth0.android.authentication.AuthenticationException
1112
import com.auth0.android.callback.Callback
1213
import com.auth0.android.dpop.DPoP
@@ -299,7 +300,8 @@ public object WebAuthProvider {
299300
}
300301
}
301302

302-
public class Builder internal constructor(private val account: Auth0) : SenderConstraining<Builder> {
303+
public class Builder internal constructor(private val account: Auth0) :
304+
SenderConstraining<Builder> {
303305
private val values: MutableMap<String, String> = mutableMapOf()
304306
private val headers: MutableMap<String, String> = mutableMapOf()
305307
private var pkce: PKCE? = null
@@ -311,6 +313,7 @@ public object WebAuthProvider {
311313
private var ctOptions: CustomTabsOptions = CustomTabsOptions.newBuilder().build()
312314
private var leeway: Int? = null
313315
private var launchAsTwa: Boolean = false
316+
private var ephemeralBrowsing: Boolean = false
314317
private var customAuthorizeUrl: String? = null
315318

316319
/**
@@ -525,6 +528,25 @@ public object WebAuthProvider {
525528
return this
526529
}
527530

531+
/**
532+
* Enable ephemeral browsing for the Custom Tab used in the login flow.
533+
* When enabled, the Custom Tab runs in an isolated session — cookies, cache,
534+
* history, and credentials are deleted when the tab closes.
535+
* Requires Chrome 136+ or a compatible browser. On unsupported browsers,
536+
* a warning is logged and a regular Custom Tab is used instead.
537+
*
538+
* **Warning:** Ephemeral browsing support in Auth0.Android is still experimental
539+
* and can change in the future. Please test it thoroughly in all the targeted browsers
540+
* and OS variants and let us know your feedback.
541+
*
542+
* @return the current builder instance
543+
*/
544+
@ExperimentalAuth0Api
545+
public fun withEphemeralBrowsing(): Builder {
546+
ephemeralBrowsing = true
547+
return this
548+
}
549+
528550
/**
529551
* Specifies a custom Authorize URL to use for this login request, overriding the default
530552
* generated from the Auth0 domain (account.authorizeUrl).
@@ -595,8 +617,15 @@ public object WebAuthProvider {
595617
values[OAuthManager.KEY_ORGANIZATION] = organizationId
596618
values[OAuthManager.KEY_INVITATION] = invitationId
597619
}
620+
621+
val effectiveCtOptions = if (ephemeralBrowsing) {
622+
ctOptions.copyWithEphemeralBrowsing()
623+
} else {
624+
ctOptions
625+
}
626+
598627
val manager = OAuthManager(
599-
account, callback, values, ctOptions, launchAsTwa,
628+
account, callback, values, effectiveCtOptions, launchAsTwa,
600629
customAuthorizeUrl, dPoP
601630
)
602631
manager.setHeaders(headers)

0 commit comments

Comments
 (0)