Secure WebViews in Flutter and React Native Apps

Short answer: To secure WebViews in Flutter and React Native, disable unnecessary JavaScript, enforce HTTPS-only navigation, validate URLs, sanitize input, handle SSL errors carefully, and clear cookies after use. Each platform offers specific configuration options to mitigate risks like XSS and MITM attacks.

Key takeaways

  • Disable JavaScript in WebView unless absolutely needed.
  • Always validate and sanitize URLs before loading.
  • Enforce HTTPS by blocking HTTP requests.
  • Clear WebView cookies and cache after sensitive sessions.
  • Use custom SSL handling only in controlled environments.
  • Implement a whitelist of allowed domains when possible.

WebViews are a convenient way to display web content inside a mobile app. You can render a payment page, show a terms-of-service document, or even run a mini web app without building native UI. But convenience comes with risk. WebViews can expose your app to cross-site scripting (XSS), man-in-the-middle (MITM) attacks, and data leakage through JavaScript bridges. If you’re building with Flutter or React Native, you need to secure your WebViews just as you would any other entry point. Here’s how.

What Makes WebViews Vulnerable?

A WebView is essentially a browser embedded in your app. It can load HTML, execute JavaScript, and communicate with your native code through a bridge. That bridge is often the weakest link. An attacker who manages to inject malicious JavaScript into the WebView can potentially access device data, execute native functions, or steal user tokens. Common attack vectors include:

  • XSS: If the WebView loads dynamic content without sanitization, an attacker can inject scripts.
  • MITM: Loading content over HTTP or using permissive SSL settings allows attackers to intercept or modify content.
  • JavaScript Bridge Abuse: Malicious scripts can call native methods exposed via the bridge if not properly restricted.

Understanding these risks is the first step. The next is knowing how to lock down each platform.

Securing WebViews in Flutter

Flutter uses the webview_flutter package, which wraps platform-specific WebView implementations. Here are the key security configurations.

Disable JavaScript When Possible

If your page is just static content, turn off JavaScript. In Flutter, you do this through the JavaScriptMode property:

WebViewController controller = WebViewController()
  ..setJavaScriptMode(JavaScriptMode.disabled);

Only enable it when the page explicitly requires JS (like a payment form).

Enforce HTTPS-Only Navigation

Use the navigationDelegate to block HTTP requests:

controller.setNavigationDelegate(NavigationDelegate(
  onNavigationRequest: (request) {
    if (!request.url.startsWith('https://')) {
      return NavigationDecision.prevent;
    }
    return NavigationDecision.navigate;
  },
));

This prevents accidental loading of insecure content. One common mistake is forgetting to also block redirects – the delegate fires for redirects too, so you’re covered.

Sanitize HTML Before Loading

If you load HTML strings directly (e.g., loadHtmlString), sanitize them first. Strip out script tags, event handlers, and unknown attributes. Use a server-side sanitizer or a trusted library like sanitize-html in the backend. Never trust user-generated HTML, even from your own backend – always sanitize.

Restrict JavaScript Bridge Communication

When using JavascriptChannel, only expose minimal methods. Validate all data coming from the WebView. Never trust messages the WebView sends to native code. For example, if a channel method takes a string, check that it doesn’t contain path traversal characters if you’re accessing files.

Securing WebViews in React Native

React Native’s react-native-webview gives you fine-grained control. Here’s how to harden it.

Disable JavaScript

Set javaScriptEnabled to false unless needed:

<WebView
  source={{ uri: 'https://example.com' }}
  javaScriptEnabled={false}
/>

Use the originWhitelist to Restrict Domains

Specify which origins can be loaded. For example, to allow only your own domain:

<WebView
  originWhitelist={['https://*.mysafeapp.com']}
/>

This prevents navigation to malicious URLs. Be careful with wildcards – prefer exact subdomains if possible.

Prevent Navigation with onShouldStartLoadWithRequest

Block any request that doesn’t match your criteria:

<WebView
  onShouldStartLoadWithRequest={(request) => {
    if (!request.url.startsWith('https://')) {
      return false;
    }
    return true;
  }}
/>

This function is also called for redirects, so you can block a redirect to an insecure site.

Secure the JavaScript Bridge

Use injectedJavaScript or postMessage carefully. When you call onMessage, validate the event data. Never execute dynamic code from the WebView in your native layer. For instance, avoid using eval on message data.

Common Mistakes to Avoid

MistakeWhy It’s Dangerous
Leaving JavaScript enabled by defaultOpens the door to XSS if content is ever compromised.
Allowing mixed content (HTTP + HTTPS)HTTP resources can be modified by an attacker in the middle.
Ignoring SSL certificate errorsBypassing SSL validation makes MITM trivial.
Not clearing cookies after useSensitive session data can be stolen if another WebView loads a malicious page.
Exposing too many native methods via bridgeEach method is a potential attack surface.

Platform-Specific Considerations

Both iOS and Android have their own security nuances. On iOS, App Transport Security (ATS) enforces HTTPS by default. If you must load HTTP, you’ll need to add an exception in Info.plist, but think twice. On Android, you can use WebViewClient.onReceivedSslError to handle SSL errors. Never call handler.proceed() in production unless you are absolutely certain.

Handling Cookies and Local Storage Securely

WebViews can store cookies and local data. If you load a page that sets authentication cookies, those cookies are accessible to subsequent pages in the same WebView. An attacker who navigates the WebView to a malicious site could steal cookies. Use the following practices:

  • Clear cookies after the WebView session ends. In React Native: WebView.clearCookies(). In Flutter: WebViewCookieManager().clearCookies().
  • Disable local storage if not needed. In Android, you can call settings.setDomStorageEnabled(false). In iOS, the WebView uses a shared process pool, so consider using a unique process pool per WebView to isolate storage.
  • Use secure cookie attributes (Secure, HttpOnly) on the server side. These prevent JavaScript from accessing cookies even if an XSS attack succeeds.

Testing Your WebView Security

Don’t just configure and forget. Test your WebView against common attacks:

  1. XSS testing: Try injecting script tags in input fields that get rendered in the WebView.
  2. URL manipulation: Attempt to navigate the WebView to http:// or local file schemes.
  3. SSL stripping: Use a proxy tool like mitmproxy to see if your WebView loads pages with bad certificates.
  4. Bridge injection: If you have a JavaScript bridge, try calling native methods from the console.

Fix any issues as you find them. Repeat the tests after each release. Automate these checks in your CI/CD pipeline if possible.

Final Takeaways

Securing WebViews is not optional. A single XSS vulnerability can compromise user data and your app’s reputation. Start with the defaults: disable JavaScript, enforce HTTPS, and validate URLs. Then layer on domain whitelisting, input sanitization, and strict bridge controls. Test actively. Your users expect you to protect their data – and now you can.

Frequently asked questions

Should I disable JavaScript in all WebViews?

Not always, but do disable it by default. Only enable JavaScript when the web content truly requires it, such as for interactive payment forms or rich media. Disabling JS removes a major XSS vector.

How do I prevent HTTPS stripping in a WebView?

Enforce HTTPS navigation by checking the request URL in the navigation delegate or onShouldStartLoadWithRequest. Block any non-HTTPS requests. Also, never ignore SSL certificate errors in production.

Can I whitelist domains in Flutter WebView?

Yes, you can use the navigationDelegate to only allow navigation to specific domains. Check the request URL against a list of allowed hosts, and prevent navigation to anything else.

Is it safe to use the JavaScript bridge in React Native WebView?

It can be safe if you minimize exposed methods and validate all incoming data. Never trust the WebView’s content. Treat messages from the WebView as untrusted input.

Should I clear cookies after using a WebView?

Yes. After a sensitive operation (like payment), clear cookies and cached data. This prevents any subsequent page loaded in the same WebView from accessing previous session data.

Leave a Comment