Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Changing the Return To URL scheme](#changing-the-return-to-url-scheme)
- [Specify a Custom Logout URL](#specify-a-custom-logout-url)
- [Trusted Web Activity](#trusted-web-activity)
- [Ephemeral Browsing [Experimental]](#ephemeral-browsing-experimental)
- [DPoP [EA]](#dpop-ea)
- [Authentication API](#authentication-api)
- [Login with database connection](#login-with-database-connection)
Expand Down Expand Up @@ -228,6 +229,42 @@ WebAuthProvider.login(account)
.await(this)
```

## Ephemeral Browsing [Experimental]

> **WARNING**
> Ephemeral browsing support in Auth0.Android is still experimental and can change in the future. Please test it thoroughly in all the targeted browsers
> and OS variants and let us know your feedback.

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.

Requires Chrome 136+ or a compatible browser. On unsupported browsers, the SDK falls back to a regular Custom Tab and logs a warning.

```kotlin
WebAuthProvider.login(account)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we also add the suspend/await example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.withEphemeralBrowsing()
.start(this, callback)
```

<details>
<summary>Using async/await</summary>

```kotlin
WebAuthProvider.login(account)
.withEphemeralBrowsing()
.await(this)
```
</details>

<details>
<summary>Using Java</summary>

```java
WebAuthProvider.login(account)
.withEphemeralBrowsing()
.start(this, callback);
```
</details>

## DPoP [EA]

> [!NOTE]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsClient;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we also need to bump the build.gradle version to 1.9.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradle is already updated the required version

import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import androidx.core.content.ContextCompat;

import com.auth0.android.annotation.ExperimentalAuth0Api;
import com.auth0.android.authentication.AuthenticationException;

import java.util.List;
Expand All @@ -26,6 +29,8 @@
*/
public class CustomTabsOptions implements Parcelable {

private static final String TAG = "CustomTabsOptions";

private final boolean showTitle;
@ColorRes
private final int toolbarColor;
Expand All @@ -34,11 +39,14 @@ public class CustomTabsOptions implements Parcelable {
@Nullable
private final List<String> disabledCustomTabsPackages;

private CustomTabsOptions(boolean showTitle, @ColorRes int toolbarColor, @NonNull BrowserPicker browserPicker, @Nullable List<String> disabledCustomTabsPackages) {
private final boolean ephemeralBrowsing;

private CustomTabsOptions(boolean showTitle, @ColorRes int toolbarColor, @NonNull BrowserPicker browserPicker, @Nullable List<String> disabledCustomTabsPackages, boolean ephemeralBrowsing) {
this.showTitle = showTitle;
this.toolbarColor = toolbarColor;
this.browserPicker = browserPicker;
this.disabledCustomTabsPackages = disabledCustomTabsPackages;
this.ephemeralBrowsing = ephemeralBrowsing;
}

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

@NonNull
CustomTabsOptions copyWithEphemeralBrowsing() {
return new CustomTabsOptions(showTitle, toolbarColor, browserPicker,
disabledCustomTabsPackages, true);
}

/**
* Create a new CustomTabsOptions.Builder instance.
*
Expand All @@ -82,6 +96,18 @@ Intent toIntent(@NonNull Context context, @Nullable CustomTabsSession session) {
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(session)
.setShowTitle(showTitle)
.setShareState(CustomTabsIntent.SHARE_STATE_OFF);

if (ephemeralBrowsing) {
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 Custom Tab.");
}
}

if (toolbarColor > 0) {
//Resource exists
final CustomTabColorSchemeParams.Builder colorBuilder = new CustomTabColorSchemeParams.Builder()
Expand All @@ -108,6 +134,7 @@ protected CustomTabsOptions(@NonNull Parcel in) {
toolbarColor = in.readInt();
browserPicker = in.readParcelable(BrowserPicker.class.getClassLoader());
disabledCustomTabsPackages = in.createStringArrayList();
ephemeralBrowsing = in.readByte() != 0;
}

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

@Override
Expand Down Expand Up @@ -147,11 +175,14 @@ public static class Builder {
@Nullable
private List<String> disabledCustomTabsPackages;

private boolean ephemeralBrowsing;

Builder() {
this.showTitle = false;
this.toolbarColor = 0;
this.browserPicker = BrowserPicker.newBuilder().build();
this.disabledCustomTabsPackages = null;
this.ephemeralBrowsing = false;
}

/**
Expand Down Expand Up @@ -212,14 +243,35 @@ public Builder withDisabledCustomTabsPackages(List<String> disabledCustomTabsPac
return this;
}

/**
* Enable ephemeral browsing for the Custom Tab.
* When enabled, the Custom Tab runs in an isolated session — cookies, cache,
* history, and credentials are deleted when the tab closes.
* Requires Chrome 136+ or a compatible browser. On unsupported browsers,
* a warning is logged and a regular Custom Tab is used instead.
* By default, ephemeral browsing is disabled.
*
* <p><b>Warning:</b> Ephemeral browsing support in Auth0.Android is still experimental
* and can change in the future. Please test it thoroughly in all the targeted browsers
* and OS variants and let us know your feedback.</p>
*
* @return this same builder instance.
*/
@ExperimentalAuth0Api
@NonNull
public Builder withEphemeralBrowsing() {
this.ephemeralBrowsing = true;
return this;
}

/**
* Create a new CustomTabsOptions instance with the customization settings.
*
* @return an instance of CustomTabsOptions with the customization settings.
*/
@NonNull
public CustomTabsOptions build() {
return new CustomTabsOptions(showTitle, toolbarColor, browserPicker, disabledCustomTabsPackages);
return new CustomTabsOptions(showTitle, toolbarColor, browserPicker, disabledCustomTabsPackages, ephemeralBrowsing);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.auth0.android.Auth0
import com.auth0.android.annotation.ExperimentalAuth0Api
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.callback.Callback
import com.auth0.android.dpop.DPoP
Expand Down Expand Up @@ -299,7 +300,8 @@ public object WebAuthProvider {
}
}

public class Builder internal constructor(private val account: Auth0) : SenderConstraining<Builder> {
public class Builder internal constructor(private val account: Auth0) :
SenderConstraining<Builder> {
private val values: MutableMap<String, String> = mutableMapOf()
private val headers: MutableMap<String, String> = mutableMapOf()
private var pkce: PKCE? = null
Expand All @@ -311,6 +313,7 @@ public object WebAuthProvider {
private var ctOptions: CustomTabsOptions = CustomTabsOptions.newBuilder().build()
private var leeway: Int? = null
private var launchAsTwa: Boolean = false
private var ephemeralBrowsing: Boolean = false
private var customAuthorizeUrl: String? = null

/**
Expand Down Expand Up @@ -525,6 +528,25 @@ public object WebAuthProvider {
return this
}

/**
* Enable ephemeral browsing for the Custom Tab used in the login flow.
* When enabled, the Custom Tab runs in an isolated session — cookies, cache,
* history, and credentials are deleted when the tab closes.
* Requires Chrome 136+ or a compatible browser. On unsupported browsers,
* a warning is logged and a regular Custom Tab is used instead.
*
* **Warning:** Ephemeral browsing support in Auth0.Android is still experimental
* and can change in the future. Please test it thoroughly in all the targeted browsers
* and OS variants and let us know your feedback.
*
* @return the current builder instance
*/
@ExperimentalAuth0Api
public fun withEphemeralBrowsing(): Builder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename this to withEphemeralSession() for cross-platform consistency?
The iOS uses useEphemeralSession()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the existing Builder methods use the with prefix. So used it to keep consistent

ephemeralBrowsing = true
return this
}

/**
* Specifies a custom Authorize URL to use for this login request, overriding the default
* generated from the Auth0 domain (account.authorizeUrl).
Expand Down Expand Up @@ -595,8 +617,15 @@ public object WebAuthProvider {
values[OAuthManager.KEY_ORGANIZATION] = organizationId
values[OAuthManager.KEY_INVITATION] = invitationId
}

val effectiveCtOptions = if (ephemeralBrowsing) {
ctOptions.copyWithEphemeralBrowsing()
} else {
ctOptions
}

val manager = OAuthManager(
account, callback, values, ctOptions, launchAsTwa,
account, callback, values, effectiveCtOptions, launchAsTwa,
customAuthorizeUrl, dPoP
)
manager.setHeaders(headers)
Expand Down
Loading
Loading