From 04fcc491667a8b24cf1cca98c34d735c995fb1d1 Mon Sep 17 00:00:00 2001 From: "jose.pereda" Date: Thu, 13 Aug 2026 11:29:57 +0200 Subject: [PATCH 1/2] Add prefersEphemeralSession flag, to avoid sharing cookies with browser --- .../attach/browser/BrowserService.java | 37 ++++++++++++++++++- .../browser/impl/AndroidBrowserService.java | 11 +++--- .../browser/impl/DesktopBrowserService.java | 4 +- .../browser/impl/IOSBrowserService.java | 7 ++-- .../src/main/native/android/c/browser.c | 9 +++-- .../android/dalvik/DalvikBrowserService.java | 7 +++- modules/browser/src/main/native/ios/Browser.m | 4 +- 7 files changed, 60 insertions(+), 19 deletions(-) diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java index 6e972065..6c44e4d7 100644 --- a/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java +++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java @@ -222,6 +222,39 @@ static Optional create() { * @throws java.net.URISyntaxException If it is not a valid URL string * @since 4.0.25 */ - void launchWebAuthentication(String url, String callbackUrlScheme, Consumer callback) - throws IOException, URISyntaxException; + default void launchWebAuthentication(String url, String callbackUrlScheme, Consumer callback) + throws IOException, URISyntaxException { + launchWebAuthentication(url, callbackUrlScheme, false, callback); + } + + /** + * Starts a web authentication session, like + * {@link #launchWebAuthentication(String, String, Consumer)}, optionally requesting an + * ephemeral browsing session. + * + *

When {@code prefersEphemeralSession} is {@code true}, the session is requested to run + * in a private/ephemeral browsing mode that does not share cookies or other browsing data + * with the browser, and discards them when the session ends. Each authentication then starts + * from a clean slate, at the cost of losing single sign-on.

+ * + *

On Android this enables ephemeral browsing on the Auth Tab (or the Custom Tab + * fallback). It is best-effort: it requires the browser to support it (e.g. recent + * Chrome versions); browsers without support ignore the request and share browsing + * data as usual. On Desktop the flag is ignored.

+ * + * @param url the authentication URL to load, including the {@code redirect_uri} expected by the + * web service. + * @param callbackUrlScheme either a custom URL scheme (without {@code ://}, e.g. {@code "myapp"}) + * or a full HTTPS URL (e.g. {@code "https://example.com/callback"}) that + * the web service uses for its redirect. + * @param prefersEphemeralSession if {@code true}, requests a private browsing session that does + * not share or persist cookies and other browsing data. + * @param callback a consumer that receives the full callback URL on success, or {@code null} if + * the user canceled the flow or an error occurred. + * @throws java.io.IOException If the URL can't be opened + * @throws java.net.URISyntaxException If it is not a valid URL string + * @since 4.0.26 + */ + void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession, + Consumer callback) throws IOException, URISyntaxException; } diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/AndroidBrowserService.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/AndroidBrowserService.java index fcc980d5..e49031e4 100644 --- a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/AndroidBrowserService.java +++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/AndroidBrowserService.java @@ -64,8 +64,8 @@ public void launchExternalBrowser(String url) throws IOException { } @Override - public void launchWebAuthentication(String url, String callbackUrlScheme, Consumer callback) - throws IOException, URISyntaxException { + public void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession, + Consumer callback) throws IOException, URISyntaxException { if (url == null || url.isEmpty()) { throw new IOException("Authentication url cannot be null or empty"); } @@ -73,15 +73,16 @@ public void launchWebAuthentication(String url, String callbackUrlScheme, Consum throw new IOException("Callback url scheme cannot be null or empty"); } if (Util.DEBUG) { - LOG.info("Launch web authentication URL: " + url + ", callback scheme: " + callbackUrlScheme); + LOG.info("Launch web authentication URL: " + url + ", callback scheme: " + callbackUrlScheme + + ", ephemeral: " + prefersEphemeralSession); } authCallback = callback; - startWebAuthentication(url, callbackUrlScheme); + startWebAuthentication(url, callbackUrlScheme, prefersEphemeralSession); } // native private native boolean launchURL(String url); - private native void startWebAuthentication(String url, String callbackUrlScheme); + private native void startWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession); // callback public static void setAuthResult(String callbackUrl) { diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DesktopBrowserService.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DesktopBrowserService.java index d9540d15..6723d330 100644 --- a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DesktopBrowserService.java +++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DesktopBrowserService.java @@ -67,8 +67,8 @@ public void launchExternalBrowser(String url) throws IOException { } @Override - public void launchWebAuthentication(String url, String callbackUrlScheme, Consumer callback) - throws IOException, URISyntaxException { + public void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession, + Consumer callback) throws IOException, URISyntaxException { launchExternalBrowser(url); } } \ No newline at end of file diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/IOSBrowserService.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/IOSBrowserService.java index e034a087..d0e8194e 100644 --- a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/IOSBrowserService.java +++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/IOSBrowserService.java @@ -54,7 +54,8 @@ public void launchExternalBrowser(String url) throws IOException { } @Override - public void launchWebAuthentication(String url, String callbackUrlScheme, Consumer callback) throws IOException { + public void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession, + Consumer callback) throws IOException { if (url == null || url.isEmpty()) { throw new IOException("Authentication url cannot be null or empty"); } @@ -62,13 +63,13 @@ public void launchWebAuthentication(String url, String callbackUrlScheme, Consum throw new IOException("Callback url scheme cannot be null or empty"); } authCallback = callback; - startWebAuthentication(url, callbackUrlScheme); + startWebAuthentication(url, callbackUrlScheme, prefersEphemeralSession); } // native private native boolean launchURL(String url); private static native void initBrowser(); - private static native void startWebAuthentication(String url, String callbackUrlScheme); + private static native void startWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession); // callback public static void setAuthResult(String callbackUrl) { diff --git a/modules/browser/src/main/native/android/c/browser.c b/modules/browser/src/main/native/android/c/browser.c index 1f0853d3..f2037250 100644 --- a/modules/browser/src/main/native/android/c/browser.c +++ b/modules/browser/src/main/native/android/c/browser.c @@ -46,7 +46,7 @@ static void initializeDalvikHandles() { ATTACH_DALVIK(); jmethodID jBrowserServiceInitMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "", "(Landroid/app/Activity;)V"); jBrowserServiceLaunchMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "launchURL", "(Ljava/lang/String;)Z"); - jBrowserServiceStartWebAuthMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "startWebAuthentication", "(Ljava/lang/String;Ljava/lang/String;)V"); + jBrowserServiceStartWebAuthMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "startWebAuthentication", "(Ljava/lang/String;Ljava/lang/String;Z)V"); jobject jActivity = substrateGetActivity(); jobject jtmpobj = (*dalvikEnv)->NewObject(dalvikEnv, jBrowserServiceClass, jBrowserServiceInitMethod, jActivity); @@ -93,17 +93,18 @@ JNIEXPORT jboolean JNICALL Java_com_gluonhq_attach_browser_impl_AndroidBrowserSe } JNIEXPORT void JNICALL Java_com_gluonhq_attach_browser_impl_AndroidBrowserService_startWebAuthentication -(JNIEnv *env, jclass jClass, jstring jurl, jstring jcallbackUrlScheme) +(JNIEnv *env, jclass jClass, jstring jurl, jstring jcallbackUrlScheme, jboolean jprefersEphemeralSession) { const char *urlChars = (*env)->GetStringUTFChars(env, jurl, NULL); const char *schemeChars = (*env)->GetStringUTFChars(env, jcallbackUrlScheme, NULL); if (isDebugAttach()) { - ATTACH_LOG_FINE("Browser start web authentication for url %s, callback scheme %s\n", urlChars, schemeChars); + ATTACH_LOG_FINE("Browser start web authentication for url %s, callback scheme %s, ephemeral %d\n", + urlChars, schemeChars, jprefersEphemeralSession); } ATTACH_DALVIK(); jstring durl = (*dalvikEnv)->NewStringUTF(dalvikEnv, urlChars); jstring dscheme = (*dalvikEnv)->NewStringUTF(dalvikEnv, schemeChars); - (*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikBrowserService, jBrowserServiceStartWebAuthMethod, durl, dscheme); + (*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikBrowserService, jBrowserServiceStartWebAuthMethod, durl, dscheme, jprefersEphemeralSession); DETACH_DALVIK(); // (*env)->ReleaseStringUTFChars(env, jurl, urlChars); // (*env)->ReleaseStringUTFChars(env, jcallbackUrlScheme, schemeChars); diff --git a/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java b/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java index 662ca268..9940c976 100644 --- a/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java +++ b/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java @@ -45,6 +45,7 @@ public class DalvikBrowserService { private static final String EXTRA_REDIRECT_SCHEME = "androidx.browser.auth.extra.REDIRECT_SCHEME"; private static final String EXTRA_HTTPS_REDIRECT_HOST = "androidx.browser.auth.extra.HTTPS_REDIRECT_HOST"; private static final String EXTRA_HTTPS_REDIRECT_PATH = "androidx.browser.auth.extra.HTTPS_REDIRECT_PATH"; + private static final String EXTRA_ENABLE_EPHEMERAL_BROWSING = "androidx.browser.customtabs.extra.ENABLE_EPHEMERAL_BROWSING"; private final Activity activity; private final boolean debug; @@ -84,7 +85,7 @@ private boolean launchURL(String url) { return true; } - private void startWebAuthentication(String url, String callbackUrlScheme) { + private void startWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession) { if (url == null || url.isEmpty() || callbackUrlScheme == null || callbackUrlScheme.isEmpty()) { Log.e(TAG, "Invalid web authentication parameters: url and callbackUrlScheme are required"); nativeWebAuthResult(null); @@ -93,6 +94,10 @@ private void startWebAuthentication(String url, String callbackUrlScheme) { Intent authIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); authIntent.putExtra(EXTRA_LAUNCH_AUTH_TAB, true); + if (prefersEphemeralSession) { + // best-effort: browsers without ephemeral browsing support ignore this extra + authIntent.putExtra(EXTRA_ENABLE_EPHEMERAL_BROWSING, true); + } // null session so browsers without Auth Tab support treat this as a Custom Tab Bundle sessionBundle = new Bundle(); sessionBundle.putBinder(EXTRA_SESSION, null); diff --git a/modules/browser/src/main/native/ios/Browser.m b/modules/browser/src/main/native/ios/Browser.m index edb562fd..14ee7c9c 100644 --- a/modules/browser/src/main/native/ios/Browser.m +++ b/modules/browser/src/main/native/ios/Browser.m @@ -128,7 +128,7 @@ void sendAuthResult(NSString *callbackUrl) { } JNIEXPORT void JNICALL Java_com_gluonhq_attach_browser_impl_IOSBrowserService_startWebAuthentication -(JNIEnv *env, jclass jClass, jstring jUrl, jstring jScheme) +(JNIEnv *env, jclass jClass, jstring jUrl, jstring jScheme, jboolean jPrefersEphemeralSession) { const jchar *charsUrl = (*env)->GetStringChars(env, jUrl, NULL); NSString *url = [NSString stringWithCharacters:(UniChar *)charsUrl length:(*env)->GetStringLength(env, jUrl)]; @@ -189,7 +189,7 @@ void sendAuthResult(NSString *callbackUrl) { if (@available(iOS 13.0, *)) { _authContextProvider = [[AttachAuthContextProvider alloc] init]; _authSession.presentationContextProvider = _authContextProvider; - _authSession.prefersEphemeralWebBrowserSession = NO; + _authSession.prefersEphemeralWebBrowserSession = (jPrefersEphemeralSession == JNI_TRUE); } dispatch_async(dispatch_get_main_queue(), ^{ From d10bc1a2537a4919144a37e21deac947e28d8670 Mon Sep 17 00:00:00 2001 From: "jose.pereda" Date: Thu, 13 Aug 2026 12:17:46 +0200 Subject: [PATCH 2/2] add warning --- .../android/dalvik/DalvikBrowserService.java | 23 +++++++++++++++++++ .../substrate/dalvik/AndroidManifest.xml | 5 ++++ 2 files changed, 28 insertions(+) diff --git a/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java b/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java index 9940c976..304b2b3b 100644 --- a/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java +++ b/modules/browser/src/main/native/android/dalvik/DalvikBrowserService.java @@ -46,6 +46,9 @@ public class DalvikBrowserService { private static final String EXTRA_HTTPS_REDIRECT_HOST = "androidx.browser.auth.extra.HTTPS_REDIRECT_HOST"; private static final String EXTRA_HTTPS_REDIRECT_PATH = "androidx.browser.auth.extra.HTTPS_REDIRECT_PATH"; private static final String EXTRA_ENABLE_EPHEMERAL_BROWSING = "androidx.browser.customtabs.extra.ENABLE_EPHEMERAL_BROWSING"; + // Custom Tabs service action and capability categories, from androidx.browser + private static final String ACTION_CUSTOM_TABS_SERVICE = "android.support.customtabs.action.CustomTabsService"; + private static final String CATEGORY_EPHEMERAL_BROWSING = "androidx.browser.customtabs.category.EphemeralBrowsing"; private final Activity activity; private final boolean debug; @@ -124,6 +127,15 @@ private void startWebAuthentication(String url, String callbackUrlScheme, boolea return; } + if (prefersEphemeralSession) { + String browserPackage = authIntent.resolveActivity(activity.getPackageManager()).getPackageName(); + if (!supportsEphemeralBrowsing(browserPackage)) { + Log.w(TAG, "The browser handling the web authentication (" + browserPackage + + ") does not support ephemeral browsing: the session will share the " + + "browser's cookies and browsing data"); + } + } + Util.setOnActivityResultHandler(new IntentHandler() { @Override public void gotActivityResult(int requestCode, int resultCode, Intent intent) { @@ -195,6 +207,17 @@ private static boolean matchesCallback(Uri uri, String callbackUrlScheme) { return callbackUrlScheme.equals(uri.getScheme()); } + /** + * Checks whether the given browser package advertises support for ephemeral browsing in its + * Custom Tabs service (equivalent to CustomTabsClient.isEphemeralBrowsingSupported). + */ + private boolean supportsEphemeralBrowsing(String browserPackage) { + Intent serviceIntent = new Intent(ACTION_CUSTOM_TABS_SERVICE) + .setPackage(browserPackage) + .addCategory(CATEGORY_EPHEMERAL_BROWSING); + return activity.getPackageManager().resolveService(serviceIntent, 0) != null; + } + private static void clearPendingSession() { pendingService = null; pendingCallbackUrlScheme = null; diff --git a/modules/browser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml b/modules/browser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml index 90b18b1b..b7b7ad6d 100644 --- a/modules/browser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml +++ b/modules/browser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml @@ -21,6 +21,11 @@ + + + +