Skip to content

Commit 3c8fc3e

Browse files
authored
Merge pull request #125 from pavikaa/feature/ephemeral_chrome_tab
Implement ephemeral custom tabs
2 parents 5456131 + 3104a5e commit 3c8fc3e

File tree

5 files changed

+64
-27
lines changed

5 files changed

+64
-27
lines changed

gradle/libs.versions.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[versions]
2-
compileSdk = "35"
3-
targetSdk = "35"
2+
compileSdk = "36"
3+
targetSdk = "36"
44
minSdk = "21"
55

66
jvmTarget = "17"
@@ -57,7 +57,7 @@ material-icons = "1.7.3"
5757
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" }
5858
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppCompat" }
5959
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
60-
androidx-browser = { module = "androidx.browser:browser", version = "1.8.0" }
60+
androidx-browser = { module = "androidx.browser:browser", version = "1.9.0-rc01" }
6161
androidx-security-crypto-ktx = { module = "androidx.security:security-crypto-ktx", version.ref = "securityCryptoKtx" }
6262
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCryptoKtx" }
6363

@@ -84,7 +84,7 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
8484
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
8585
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
8686
ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
87-
ktor-client-contentnegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor"}
87+
ktor-client-contentnegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
8888
ktor-client-serialization = { module = "io.ktor:ktor-client-serialization", version.ref = "ktor" }
8989
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
9090
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
@@ -97,7 +97,7 @@ ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor"
9797

9898
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
9999
assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "assertk" }
100-
material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "material-icons"}
100+
material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "material-icons" }
101101

102102
# Build logic dependencies
103103
android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
@@ -124,10 +124,10 @@ android-library = { id = "com.android.library", version.ref = "agp" }
124124
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
125125
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
126126
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
127-
kmp = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
127+
kmp = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
128128
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
129129
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
130-
nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish-plugin"}
130+
nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish-plugin" }
131131
multiplatform-swiftpackage = { id = "io.github.luca992.multiplatform-swiftpackage", version.ref = "multiplatform-swiftpackage" }
132132
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
133133
swiftklib = { id = "io.github.ttypic.swiftklib", version.ref = "swiftklib" }

oidc-appsupport/src/androidMain/kotlin/org/publicvalue/multiplatform/oidc/appsupport/AndroidCodeAuthFlowFactory.kt

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,21 @@ import org.publicvalue.multiplatform.oidc.flows.EndSessionFlow
2323
*/
2424
@Suppress("unused")
2525
class AndroidCodeAuthFlowFactory(
26-
/** If true, uses an embedded WebView instead of Chrome CustomTab (not recommended) **/
26+
/**
27+
* If `true`, an embedded WebView is used for the authorization flow.
28+
* This is generally **not recommended** due to security and UX concerns.
29+
*
30+
* If `false` (default), Chrome Custom Tabs are preferred (if available),
31+
* falling back to a WebView only if no suitable browser is found.
32+
*/
2733
private val useWebView: Boolean = false,
28-
/** Clear cache and cookies in WebView **/
29-
private val webViewEpheremalSession: Boolean = false,
34+
35+
/**
36+
* If `true`, the authorization session will be ephemeral:
37+
* cookies, cache, and other session data will be cleared before starting
38+
* the flow in both WebView and Custom Tabs (if supported).
39+
*/
40+
private val ephemeralSession: Boolean = false,
3041
/** preferred custom tab providers, list of package names in order of priority. Check [Browser][org.publicvalue.multiplatform.oidc.appsupport.customtab.Browser] for example values. **/
3142
private val customTabProviderPriority: List<String> = listOf()
3243
): CodeAuthFlowFactory {
@@ -70,18 +81,19 @@ class AndroidCodeAuthFlowFactory(
7081
}
7182

7283
override fun createAuthFlow(client: OpenIdConnectClient): PlatformCodeAuthFlow {
84+
val customTabProviders = context.getCustomTabProviders().map { it.activityInfo.packageName }
7385
val preferredBrowserPackage = if (customTabProviderPriority.isNotEmpty()) {
74-
val customTabProviders = context.getCustomTabProviders().map { it.activityInfo.packageName }
75-
val presentPreferredProviders = customTabProviderPriority.filter { customTabProviders.contains(it) }
86+
val presentPreferredProviders =
87+
customTabProviderPriority.filter { customTabProviders.contains(it) }
7688
presentPreferredProviders.firstOrNull()
77-
} else null
89+
} else customTabProviders.firstOrNull()
7890

7991
return PlatformCodeAuthFlow(
8092
context = context,
8193
contract = activityResultLauncher,
8294
client = client,
8395
useWebView = useWebView,
84-
webViewEpheremalSession = webViewEpheremalSession,
96+
ephemeralSession = ephemeralSession,
8597
preferredBrowserPackage = preferredBrowserPackage
8698
)
8799
}

oidc-appsupport/src/androidMain/kotlin/org/publicvalue/multiplatform/oidc/appsupport/HandleRedirectActivity.kt

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.publicvalue.multiplatform.oidc.appsupport
22

33
import android.content.Intent
4-
import android.net.Uri
54
import android.os.Bundle
65
import android.view.ViewGroup
76
import android.webkit.CookieManager
@@ -11,14 +10,16 @@ import android.webkit.WebSettings
1110
import android.webkit.WebView
1211
import android.webkit.WebViewClient
1312
import androidx.activity.ComponentActivity
13+
import androidx.browser.customtabs.CustomTabsClient
1414
import androidx.browser.customtabs.CustomTabsIntent
15+
import androidx.core.net.toUri
1516
import androidx.core.view.ViewCompat
1617
import androidx.core.view.WindowInsetsCompat
1718
import androidx.core.view.updateLayoutParams
1819
import org.publicvalue.multiplatform.oidc.ExperimentalOpenIdConnect
1920

2021
internal const val EXTRA_KEY_USEWEBVIEW = "usewebview"
21-
internal const val EXTRA_KEY_WEBVIEW_EPHEREMAL_SESSION = "webview_epheremal_session"
22+
internal const val EXTRA_KEY_EPHEMERAL_SESSION = "ephemeral_session"
2223
internal const val EXTRA_KEY_REDIRECTURL = "redirecturl"
2324
internal const val EXTRA_KEY_URL = "url"
2425
internal const val EXTRA_KEY_PACKAGE_NAME = "package"
@@ -107,7 +108,7 @@ class HandleRedirectActivity : ComponentActivity() {
107108
override fun onResume() {
108109
super.onResume()
109110
val useWebView = intent.extras?.getBoolean(EXTRA_KEY_USEWEBVIEW)
110-
val webViewEpheremalSession = intent.extras?.getBoolean(EXTRA_KEY_WEBVIEW_EPHEREMAL_SESSION)
111+
val ephemeralSession = intent.extras?.getBoolean(EXTRA_KEY_EPHEMERAL_SESSION)
111112
val url = intent.extras?.getString(EXTRA_KEY_URL)
112113
val redirectUrl = intent.extras?.getString(EXTRA_KEY_REDIRECTURL)
113114

@@ -129,24 +130,48 @@ class HandleRedirectActivity : ComponentActivity() {
129130
// do not navigate to the login page again in this activity instance
130131
intent.removeExtra(EXTRA_KEY_URL)
131132
// get preferred browser if set
132-
val browserPackage = intent.extras?.getString(EXTRA_KEY_PACKAGE_NAME)
133+
val preferredBrowserPackage = intent.extras?.getString(EXTRA_KEY_PACKAGE_NAME)
133134
intent.removeExtra(EXTRA_KEY_PACKAGE_NAME)
134135
if (useWebView == true) {
135-
showWebView(url, redirectUrl, webViewEpheremalSession ?: false)
136+
showWebView(url, redirectUrl, ephemeralSession ?: false)
136137
} else {
137-
launchCustomTabsIntent(url, browserPackage)
138+
launchCustomTabsIntent(
139+
url,
140+
redirectUrl,
141+
preferredBrowserPackage,
142+
ephemeralSession
143+
)
138144
}
139145
}
140146
}
141147
}
142148

143-
private fun launchCustomTabsIntent(url: String?, browserPackage: String?) {
149+
@OptIn(ExperimentalOpenIdConnect::class)
150+
private fun launchCustomTabsIntent(
151+
url: String,
152+
redirectUrl: String?,
153+
preferredBrowserPackage: String?,
154+
ephemeralSession: Boolean?
155+
) {
144156
val builder = CustomTabsIntent.Builder()
145157
builder.configureCustomTabsIntent()
158+
159+
if (preferredBrowserPackage != null) {
160+
// Enable ephemeral browsing if supported
161+
if (CustomTabsClient.isEphemeralBrowsingSupported(this, preferredBrowserPackage)) {
162+
builder.setEphemeralBrowsingEnabled(ephemeralSession ?: false)
163+
}
164+
} else {
165+
// If custom tabs are not available, fallback to WebView
166+
showWebView(url, redirectUrl, ephemeralSession ?: false)
167+
return
168+
}
169+
170+
146171
val intent = builder.build()
147172

148-
browserPackage?.let { intent.intent.setPackage(it) }
149-
intent.launchUrl(this, Uri.parse(url))
173+
preferredBrowserPackage.let { intent.intent.setPackage(it) }
174+
intent.launchUrl(this, url.toUri())
150175
}
151176

152177
override fun onNewIntent(intent: Intent) {

oidc-appsupport/src/androidMain/kotlin/org/publicvalue/multiplatform/oidc/appsupport/PlatformCodeAuthFlow.android.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ actual class PlatformCodeAuthFlow(
2121
context: Context,
2222
contract: ActivityResultLauncherSuspend<Intent, ActivityResult>,
2323
useWebView: Boolean = false,
24-
webViewEpheremalSession: Boolean = false,
24+
ephemeralSession: Boolean = false,
2525
preferredBrowserPackage: String? = null,
2626
actual override val client: OpenIdConnectClient,
2727
) : CodeAuthFlow, EndSessionFlow {
@@ -30,7 +30,7 @@ actual class PlatformCodeAuthFlow(
3030
context = context,
3131
contract = contract,
3232
useWebView = useWebView,
33-
webViewEpheremalSession = webViewEpheremalSession,
33+
ephemeralSession = ephemeralSession,
3434
preferredBrowserPackage = preferredBrowserPackage
3535
)
3636

oidc-appsupport/src/androidMain/kotlin/org/publicvalue/multiplatform/oidc/appsupport/WebActivityFlow.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ internal class WebActivityFlow(
99
private val context: Context,
1010
private val contract: ActivityResultLauncherSuspend<Intent, ActivityResult>,
1111
private val useWebView: Boolean,
12-
private val webViewEpheremalSession: Boolean,
12+
private val ephemeralSession: Boolean,
1313
private val preferredBrowserPackage: String?,
1414
) {
1515
internal suspend fun startWebFlow(requestUrl: Url, redirectUrl: String): ActivityResult {
@@ -26,10 +26,10 @@ internal class WebActivityFlow(
2626
.apply {
2727
this.putExtra(EXTRA_KEY_URL, requestUrl)
2828
this.putExtra(EXTRA_KEY_PACKAGE_NAME, preferredBrowserPackage)
29+
this.putExtra(EXTRA_KEY_EPHEMERAL_SESSION, ephemeralSession)
2930
if (useWebView) {
3031
this.putExtra(EXTRA_KEY_USEWEBVIEW, true)
3132
this.putExtra(EXTRA_KEY_REDIRECTURL, redirectUrl)
32-
this.putExtra(EXTRA_KEY_WEBVIEW_EPHEREMAL_SESSION, webViewEpheremalSession)
3333
}
3434
}
3535
return intent

0 commit comments

Comments
 (0)