-
Notifications
You must be signed in to change notification settings - Fork 167
feat: Add support for ephemeral session for chrome custom tabs #916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't we also need to bump the build.gradle version to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
|
@@ -26,6 +29,8 @@ | |
| */ | ||
| public class CustomTabsOptions implements Parcelable { | ||
|
|
||
| private static final String TAG = "CustomTabsOptions"; | ||
|
|
||
| private final boolean showTitle; | ||
| @ColorRes | ||
| private final int toolbarColor; | ||
|
|
@@ -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 | ||
|
|
@@ -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. | ||
| * | ||
|
|
@@ -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() | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
@@ -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; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -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); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
||
| /** | ||
|
|
@@ -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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we rename this to withEphemeralSession() for cross-platform consistency?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the existing Builder methods use the |
||
| 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). | ||
|
|
@@ -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) | ||
|
|
||
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done