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:
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:
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.
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.