App Bound Domains

I want to be able show web content from a limited number of domains in my app using a WKWebView. I don’t want a user to navigate away from those domains and browse the wider internet. Apple introduced App Bound Domains in iOS 14 to make that easier.

I’m using some sample code from when I was experimenting with dynamic type in web views. The HTML content is loaded into a WKWebView from a file and includes a link to this website:

Static web content in a WKWebView

I want the user to be able navigate to that domain but not to be able to escape to the general web where they may be exposed to third-party tracking that is not consistent with the privacy policy for the app.

Opt-In To App Bound Domains

The WebKit framework added the App Bound Domains feature in iOS 14. It limits a WKWebview to a list of known domains you specify in the Info.plist file for the app target. Add an Array with the key WKAppBoundDomains and then list the domains:

WKAppBoundDomains key in Info.plist with single entry for useyourloaf.com

I can now follow the link to useyourloaf.com and browse that domain. I cannot follow a link to another domain like github.com. If I try the navigation fails:

WebPageProxy::Ignoring request to load this main resource because it is attempting to navigate away from an app-bound domain or navigate after using restricted APIs

You can add up to ten domains to WKAppBoundDomains. If you add more, the extra domains are ignored and navigation to those domains will fail.

WKAppBoundDomains key in Info.plist with three entries for useyourloaf.com, github.com and apple.com

The app bound domains are typically the core domains used by your app that you control or trust to comply with your app’s privacy policy. If you need a more flexible approach you can use the WKNavigationDelegate to check the request before returning a policy (allow or cancel):

func webView(_ webView: WKWebView,
  decidePolicyFor navigationAction: WKNavigationAction,
  decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
  if let url = navigationAction.request.url,
    isDomainAllowed(url) {
    decisionHandler(.allow)
  } else {
    decisionHandler(.cancel)
  }
}

Note that this method is never called if you also implement the delegate method webView(_:decidePolicyFor:preferences:decisionHandler:). You’ll need to move the url check to that method.

Disable All Javascript

If your web content does not use any javascript you can completely disable it. This is a bit of a blunt instrument these days given how much of the web relies on javascript. In iOS 13, you did this using the WKPreferences property when creating the WKWebView configuration:

let configuration = WKWebViewConfiguration()
let preferences = WKPreferences()
preferences.javaScriptEnabled = false
configuration.preferences = preferences
 
let webView = WKWebView(frame: .zero,
                configuration: configuration)

Apple deprecated this approach with iOS 14. Instead you set WKWebpagePreferences in the WKNavigationDelegate method:

// This method requires iOS 13
func webView(_ webView: WKWebView,
  decidePolicyFor navigationAction: WKNavigationAction,
  preferences: WKWebpagePreferences,
  decisionHandler: @escaping
  (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
  if #available(iOS 14.0, *) {
    preferences.allowsContentJavaScript = false
  }
  decisionHandler(.allow, preferences)
}

Use this method in place of the older delegate method we saw earlier to perform checks on the request before deciding the policy and preferences to return in the decision handler. If you implement both delegate methods only this later method is called.

Get The Code

Read More