Skip to content

Commit 5f751f5

Browse files
authored
Enable passkey support in browser for internal builds (#6550)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1203822806345703/task/1211006058270758?focus=true ### Description Enables `passkey` support for `internal` builds. - Currently the browser reports to websites that passkeys are unavailable - With this change, we add support for passkeys that can be used to log in and register on websites. We do not store passkeys in our app, but instead allow integration with the system's passkey provider (Google Password Manager, Samsung Pass etc...) It's `internal` only to give us some time to see if it brings issues, get internal feedback etc... ℹ️ Passkey support depends on which certificate is used to sign the app. As a browser we have been granted a special exemption for using APIs around origin but that relies on certificate hash matches. As such, to test this out in development you will need to [Set up debug code signing to use whitelisted certificate](https://app.asana.com/1/137249556945/task/1208295420929846?focus=true) ### Steps to test this PR - Ensure you are testing on an up-to-date version of `WebView` and Android API. - If using an emulator, for best results use one with Google Play installed. #### Verify browser shows as supported - [x] Follow instructions in [Set up debug code signing to use whitelisted certificate](https://app.asana.com/1/137249556945/task/1208295420929846?focus=true) - [x] Install from this branch - [x] Visit https://webauthn.io; verify you **do not see** a message saying browser is unsupported. (if you do, ping me) #### Verify you can create a new `passkey` - [x] Enter username (e.g., `chicken`) and tap `Register`; verify you are prompted to create a passkey. - [x] do it. (you might be prompted to set up device password if not already set up) - [x] Verify you see a `success` message. Tap the `Try it again` button to return. #### Verify you can login when username already selected - [x] Ensure your username is still filled in (e.g., `chicken`) and tap the `Authenticate` button - [x] Verify you see the `You're logged in` page. Tap the `Try it again` button to return #### Create a 2nd passkey for this website - [x] Enter another username (e.g., `horse`) and tap `Register` button. Accept the prompt to create a new passkey. - [x] Clear out the username field so it's empty - [x] Tap on `Authenticate` button and verify you see a list of passkeys and that selecting one lets you log in Co-authored-by: Craig Russell <[email protected]>
1 parent 21348ab commit 5f751f5

File tree

5 files changed

+87
-0
lines changed

5 files changed

+87
-0
lines changed

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ import com.duckduckgo.app.browser.viewstate.LoadingViewState
176176
import com.duckduckgo.app.browser.viewstate.OmnibarViewState
177177
import com.duckduckgo.app.browser.viewstate.PrivacyShieldViewState
178178
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
179+
import com.duckduckgo.app.browser.webauthn.WebViewPasskeyInitializer
179180
import com.duckduckgo.app.browser.webshare.WebShareChooser
180181
import com.duckduckgo.app.browser.webview.WebContentDebugging
181182
import com.duckduckgo.app.browser.webview.WebViewBlobDownloadFeature
@@ -583,6 +584,9 @@ class BrowserTabFragment :
583584
@Inject
584585
lateinit var androidBrowserConfigFeature: AndroidBrowserConfigFeature
585586

587+
@Inject
588+
lateinit var passkeyInitializer: WebViewPasskeyInitializer
589+
586590
/**
587591
* We use this to monitor whether the user was seeing the in-context Email Protection signup prompt
588592
* This is needed because the activity stack will be cleared if an external link is opened in our browser
@@ -3111,6 +3115,10 @@ class BrowserTabFragment :
31113115
}
31123116

31133117
WebView.setWebContentsDebuggingEnabled(webContentDebugging.isEnabled())
3118+
3119+
lifecycleScope.launch {
3120+
webView?.let { passkeyInitializer.configurePasskeySupport(it) }
3121+
}
31143122
}
31153123

31163124
private fun screenLock(data: JsCallbackData) {

app/src/main/java/com/duckduckgo/app/browser/DuckDuckGoWebView.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,10 @@ class DuckDuckGoWebView : WebView, NestedScrollingChild3 {
412412
}
413413
}
414414

415+
fun isDestroyed(): Boolean {
416+
return isDestroyed
417+
}
418+
415419
@SuppressLint("RequiresFeature", "AddWebMessageListenerUsage")
416420
suspend fun safeAddWebMessageListener(
417421
webViewCapabilityChecker: WebViewCapabilityChecker,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.webauthn
18+
19+
import android.annotation.SuppressLint
20+
import androidx.webkit.WebSettingsCompat
21+
import androidx.webkit.WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_FOR_BROWSER
22+
import androidx.webkit.WebViewFeature
23+
import androidx.webkit.WebViewFeature.WEB_AUTHENTICATION
24+
import com.duckduckgo.app.browser.DuckDuckGoWebView
25+
import com.duckduckgo.autofill.api.AutofillFeature
26+
import com.duckduckgo.common.utils.DispatcherProvider
27+
import com.duckduckgo.di.scopes.AppScope
28+
import com.squareup.anvil.annotations.ContributesBinding
29+
import javax.inject.Inject
30+
import kotlinx.coroutines.withContext
31+
import logcat.logcat
32+
33+
interface WebViewPasskeyInitializer {
34+
suspend fun configurePasskeySupport(webView: DuckDuckGoWebView)
35+
}
36+
37+
@ContributesBinding(AppScope::class)
38+
class RealWebViewPasskeyInitializer @Inject constructor(
39+
private val autofillFeature: AutofillFeature,
40+
private val dispatchers: DispatcherProvider,
41+
) : WebViewPasskeyInitializer {
42+
43+
override suspend fun configurePasskeySupport(webView: DuckDuckGoWebView) {
44+
if (featureFlagEnabled() && webViewCapable()) {
45+
enablePasskeySupport(webView)
46+
}
47+
}
48+
49+
@SuppressLint("RequiresFeature")
50+
private suspend fun enablePasskeySupport(webView: DuckDuckGoWebView) {
51+
withContext(dispatchers.main()) {
52+
if (!webView.isDestroyed()) {
53+
WebSettingsCompat.setWebAuthenticationSupport(webView.settings, WEB_AUTHENTICATION_SUPPORT_FOR_BROWSER)
54+
logcat { "Autofill-passkey: WebView passkey support (WebAuthn) enabled" }
55+
}
56+
}
57+
}
58+
59+
private suspend fun featureFlagEnabled(): Boolean {
60+
return withContext(dispatchers.io()) {
61+
autofillFeature.passkeySupport().isEnabled()
62+
}
63+
}
64+
65+
private suspend fun webViewCapable(): Boolean {
66+
return withContext(dispatchers.main()) {
67+
WebViewFeature.isFeatureSupported(WEB_AUTHENTICATION)
68+
}
69+
}
70+
}

autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,7 @@ interface AutofillFeature {
154154

155155
@Toggle.DefaultValue(defaultValue = DefaultFeatureValue.TRUE)
156156
fun canShowImportOptionInAppSettings(): Toggle
157+
158+
@Toggle.DefaultValue(defaultValue = DefaultFeatureValue.INTERNAL)
159+
fun passkeySupport(): Toggle
157160
}

autofill/autofill-impl/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="com.duckduckgo.autofill.impl">
44

5+
<uses-permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN" />
6+
57
<application>
68
<activity
79
android:name=".ui.credential.management.importpassword.ImportPasswordsActivity"

0 commit comments

Comments
 (0)