Skip to content

Commit 1f2835d

Browse files
committed
[webview_flutter_android] Add Web Authentication support
- Adds setWebAuthenticationSupport() method to configure WebAuthn support - Supports three levels: none (disabled), forApp, forBrowser - Updates Pigeon bindings and generated code - Includes comprehensive tests for all support levels - Updates documentation and examples - Bumps version to 4.13.0
1 parent 1b56cde commit 1f2835d

14 files changed

Lines changed: 2860 additions & 1517 deletions

File tree

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## 4.13.0
22

33
* Adds new method for accessing a native `WebView` from a `FlutterPluginBinding`.
4+
* Adds support for configuring Web Authentication in `AndroidWebViewController` with `setWebAuthenticationSupport`.
5+
* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.
46

57
## 4.12.2
68

packages/webview_flutter/webview_flutter_android/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,34 @@ Add intent filters to your AndroidManifest.xml to discover and invoke Android pa
8484
</queries>
8585
```
8686

87+
## Enable Web Authentication in WebView
88+
89+
WebAuthentication (WebAuthn) can be configured by calling
90+
`AndroidWebViewController.setWebAuthenticationSupport` after checking
91+
`AndroidWebViewController.isWebViewFeatureSupported`.
92+
93+
The WebAuthentication support level can be set to one of three values:
94+
- **[WebAuthenticationSupport.none]**: Disables all WebAuthn requests
95+
- **[WebAuthenticationSupport.forApp]**: Allows WebAuthn for the embedded application (default)
96+
- **[WebAuthenticationSupport.forBrowser]**: Allows WebAuthn for any website (browser-like behavior)
97+
98+
<?code-excerpt "example/lib/readme_excerpts.dart (web_authentication_example)"?>
99+
```dart
100+
final bool webAuthenticationSupported = await androidController
101+
.isWebViewFeatureSupported(WebViewFeatureType.webAuthentication);
102+
103+
if (webAuthenticationSupported) {
104+
// Enable WebAuthn for the embedded app
105+
await androidController.setWebAuthenticationSupport(
106+
WebAuthenticationSupport.forApp,
107+
);
108+
// Or for browser-like behavior supporting any website:
109+
// await androidController.setWebAuthenticationSupport(
110+
// WebAuthenticationSupport.forBrowser,
111+
// );
112+
}
113+
```
114+
87115
## Fullscreen Video
88116

89117
To display a video as fullscreen, an app must manually handle the notification that the current page

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6826,6 +6826,8 @@ abstract class PigeonApiWebSettingsCompat(
68266826
) {
68276827
abstract fun setPaymentRequestEnabled(webSettings: android.webkit.WebSettings, enabled: Boolean)
68286828

6829+
abstract fun setWebAuthenticationSupport(webSettings: android.webkit.WebSettings, support: Long)
6830+
68296831
companion object {
68306832
@Suppress("LocalVariableName")
68316833
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebSettingsCompat?) {
@@ -6854,6 +6856,30 @@ abstract class PigeonApiWebSettingsCompat(
68546856
channel.setMessageHandler(null)
68556857
}
68566858
}
6859+
run {
6860+
val channel =
6861+
BasicMessageChannel<Any?>(
6862+
binaryMessenger,
6863+
"dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.setWebAuthenticationSupport",
6864+
codec)
6865+
if (api != null) {
6866+
channel.setMessageHandler { message, reply ->
6867+
val args = message as List<Any?>
6868+
val webSettingsArg = args[0] as android.webkit.WebSettings
6869+
val supportArg = args[1] as Long
6870+
val wrapped: List<Any?> =
6871+
try {
6872+
api.setWebAuthenticationSupport(webSettingsArg, supportArg)
6873+
listOf(null)
6874+
} catch (exception: Throwable) {
6875+
AndroidWebkitLibraryPigeonUtils.wrapError(exception)
6876+
}
6877+
reply.reply(wrapped)
6878+
}
6879+
} else {
6880+
channel.setMessageHandler(null)
6881+
}
6882+
}
68576883
}
68586884
}
68596885

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,28 @@ public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) {
2929
public void setPaymentRequestEnabled(@NonNull WebSettings webSettings, boolean enabled) {
3030
WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled);
3131
}
32+
33+
/**
34+
* This method should only be called if {@link WebViewFeatureProxyApi#isFeatureSupported(String)}
35+
* with WEB_AUTHENTICATION returns true.
36+
*
37+
* <p>The {@code support} parameter is a {@code long} to accommodate Dart's integer type, but is
38+
* safely converted to {@code int} for the underlying Android API call. {@link
39+
* Math#toIntExact(long)} is used to verify the value fits in the {@code int} range and throw
40+
* {@link ArithmeticException} if it overflows. This is safe because the valid support levels are
41+
* constants (0, 1, 2) that well within the integer range.
42+
*
43+
* <p>Note: {@link Math#toIntExact(long)} requires API level 24 or higher. This is compatible with
44+
* this plugin's minimum SDK version.
45+
*
46+
* @param webSettings the WebSettings instance
47+
* @param support the WebAuthentication support level (0, 1, or 2)
48+
* @throws ArithmeticException if {@code support} exceeds {@link Integer#MAX_VALUE}
49+
*/
50+
@SuppressLint("RequiresFeature")
51+
@Override
52+
public void setWebAuthenticationSupport(@NonNull WebSettings webSettings, long support) {
53+
final int supportValue = Math.toIntExact(support);
54+
WebSettingsCompat.setWebAuthenticationSupport(webSettings, supportValue);
55+
}
3256
}

packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package io.flutter.plugins.webviewflutter;
66

7+
import static org.junit.Assert.assertThrows;
78
import static org.junit.Assert.fail;
89
import static org.mockito.Mockito.mock;
910
import static org.mockito.Mockito.mockStatic;
@@ -36,4 +37,38 @@ public void setPaymentRequestEnabled() {
3637
fail(e.toString());
3738
}
3839
}
40+
41+
@Test
42+
public void setWebAuthenticationSupport() {
43+
final PigeonApiWebSettingsCompat api =
44+
new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat();
45+
46+
final WebSettings webSettings = mock(WebSettings.class);
47+
48+
try (MockedStatic<WebSettingsCompat> mockedStatic = mockStatic(WebSettingsCompat.class);
49+
MockedStatic<WebViewFeature> mockedWebViewFeature = mockStatic(WebViewFeature.class)) {
50+
mockedWebViewFeature
51+
.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.WEB_AUTHENTICATION))
52+
.thenReturn(true);
53+
api.setWebAuthenticationSupport(webSettings, 2L);
54+
mockedStatic.verify(() -> WebSettingsCompat.setWebAuthenticationSupport(webSettings, 2));
55+
}
56+
}
57+
58+
@Test
59+
public void setWebAuthenticationSupportWithLongOutsideIntRangeThrows() {
60+
final PigeonApiWebSettingsCompat api =
61+
new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat();
62+
63+
final WebSettings webSettings = mock(WebSettings.class);
64+
65+
try (MockedStatic<WebViewFeature> mockedWebViewFeature = mockStatic(WebViewFeature.class)) {
66+
mockedWebViewFeature
67+
.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.WEB_AUTHENTICATION))
68+
.thenReturn(true);
69+
assertThrows(
70+
ArithmeticException.class,
71+
() -> api.setWebAuthenticationSupport(webSettings, Integer.MAX_VALUE + 1L));
72+
}
73+
}
3974
}

packages/webview_flutter/webview_flutter_android/example/lib/main.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ enum MenuOptions {
329329
videoExample,
330330
logExample,
331331
basicAuthentication,
332+
webAuthentication,
332333
javaScriptAlert,
333334
viewportMeta,
334335
}
@@ -383,6 +384,8 @@ class SampleMenu extends StatelessWidget {
383384
_onLogExample();
384385
case MenuOptions.basicAuthentication:
385386
_promptForUrl(context);
387+
case MenuOptions.webAuthentication:
388+
_onWebAuthenticationExample(context);
386389
case MenuOptions.javaScriptAlert:
387390
_onJavaScriptAlertExample(context);
388391
case MenuOptions.viewportMeta:
@@ -443,6 +446,10 @@ class SampleMenu extends StatelessWidget {
443446
value: MenuOptions.basicAuthentication,
444447
child: Text('Basic Authentication Example'),
445448
),
449+
const PopupMenuItem<MenuOptions>(
450+
value: MenuOptions.webAuthentication,
451+
child: Text('Web Authentication Example'),
452+
),
446453
const PopupMenuItem<MenuOptions>(
447454
value: MenuOptions.javaScriptAlert,
448455
child: Text('JavaScript Alert Example'),
@@ -564,6 +571,36 @@ class SampleMenu extends StatelessWidget {
564571
);
565572
}
566573

574+
Future<void> _onWebAuthenticationExample(BuildContext context) async {
575+
final androidController = webViewController as AndroidWebViewController;
576+
final bool supported = await androidController.isWebViewFeatureSupported(
577+
WebViewFeatureType.webAuthentication,
578+
);
579+
580+
if (!supported) {
581+
if (context.mounted) {
582+
ScaffoldMessenger.of(context).showSnackBar(
583+
const SnackBar(
584+
content: Text(
585+
'Web Authentication is not supported on this device.',
586+
),
587+
),
588+
);
589+
}
590+
return;
591+
}
592+
593+
await androidController.setWebAuthenticationSupport(
594+
WebAuthenticationSupport.forApp,
595+
);
596+
597+
if (context.mounted) {
598+
ScaffoldMessenger.of(context).showSnackBar(
599+
const SnackBar(content: Text('Web Authentication enabled.')),
600+
);
601+
}
602+
}
603+
567604
Future<void> _onDoPostRequest() {
568605
return webViewController.loadRequest(
569606
LoadRequestParams(

packages/webview_flutter/webview_flutter_android/example/lib/readme_excerpts.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,29 @@ Future<void> enablePaymentRequest() async {
2020
// #enddocregion payment_request_example
2121
}
2222

23+
/// Example function for README demonstration of Web Authentication support.
24+
Future<void> enableWebAuthentication() async {
25+
final controller = PlatformWebViewController(
26+
AndroidWebViewControllerCreationParams(),
27+
);
28+
final androidController = controller as AndroidWebViewController;
29+
// #docregion web_authentication_example
30+
final bool webAuthenticationSupported = await androidController
31+
.isWebViewFeatureSupported(WebViewFeatureType.webAuthentication);
32+
33+
if (webAuthenticationSupported) {
34+
// Enable WebAuthn for the embedded app
35+
await androidController.setWebAuthenticationSupport(
36+
WebAuthenticationSupport.forApp,
37+
);
38+
// Or for browser-like behavior supporting any website:
39+
// await androidController.setWebAuthenticationSupport(
40+
// WebAuthenticationSupport.forBrowser,
41+
// );
42+
}
43+
// #enddocregion web_authentication_example
44+
}
45+
2346
/// Example function for README demonstration of geolocation permissions for
2447
/// a use case where the content is always trusted (for example, it only shows
2548
/// content from a domain controlled by the app developer) and geolocation

0 commit comments

Comments
 (0)