The Intelligent Tracking Prevention
From Version 1.0 to 2.3
Apple introduced the first version of Intelligent Tracking Prevention (ITP) in June 2017. Since then, it was included in the operating system’s Safari browser (macOS, iPadOS, iOS) and updated over time.
The principal task of the ITP is to keep the user more private when browsing the internet and prevent tracking by advertising companies like Facebook, Google, and more. If you want to read more about ITP, you can head to webkit.org:
Everything was fine when “just” browsing, but in iOS 14 (or iPadOS 14), the ITP (in version 2.3) was also activated (“on” by default) in the WKWebView. Now things are getting worse. What does this mean for hybrid apps? Let’s look at one specific scenario used in many apps:
The OAuth Implicit Flow Based on Cookies with Silent Refresh
When a hybrid app uses the Implicit Flow, it redirects to an IDP where the login happens and gets an access token back appended to the return URL. The IDP itself stores a long-living cookie that identifies the user.
The client app can do a silent refresh via an iframe when the current short-living access token expires while this cookie is available. During the login accessing the IDP is the “First-Party” request. To clarify, a “First-Party” is everything under the browser’s main address hosting the current site. Anything with a different domain is a “Third-Party” instead. In the ITP’s point of view, this is all fine; the browser will store the cookie as usual. The silent refresh happens in an iframe; the address of the IDP isn’t a “First-Party” request anymore as the hybrid app is creating the iframe in its DOM. It is now a “Third-Party” request, and this is where the Intelligent Tracking Prevention kicks in.
In that scenario, the IDP cannot access any of its cookies and cannot identify the user anymore. The silent refresh fails. When this happens, the client app needs to redirect to the IDP for (re-)login because the access tokens will expire soon. The “strange” thing in that moment is, the IDP is now the “First-Party” again and, of course, can read its cookies and still identify the user. It results in an instant redirect to the client app’s return URL with a new access token most of the time. But the user’s workflow is ultimately disturbed; the user may lose unsaved data during the redirect.
Some more things ITP does
The ITP has many more filters and functions to protect the privacy of the user. Another mentionable part is the seven-day cap on all script-writeable storage. If a user isn’t interacting with a website for seven days, ITP is deleting all data in the following storage forms:
- Media keys
- Service Worker registrations and cache
But is this a problem when using the WKWebView? No. It does not happen, as the WKWebView isn’t part of the primary Safari process and has its independent day counter resetting every time it starts. The same behavior occurs for a PWA added to the home screen. A new WebApp process spawns, isolated from Safari with its own counter.
Seeing the Problem in Action
I have created a sample repository containing an Angular app and a Cordova project. With the simple Express webserver responsible for handling the cookies, you can simulate the identity flow behavior in the WKWebView used in the Cordova project. I added the following entries to Cordova’s
config.xml, as this is the recommended way to go since version 6.0 of “cordova-ios”:
This switches from file:// protocol-based serving of the app to a custom URL scheme-based. Usually, you should use the latter one; otherwise, you may have some severe CORS issues.
If you want to run it on your Mac, you should have some prerequisites installed:
Install mkcert with Homebrew
To create a self-signed (trusted) SSL certificate, the easiest way to do this is to use mkcert. Just install it with Homebrew and add its CA to your trusted authorities:
brew install mkcert mkcert -install
Create the necessary SSL certificates by running:
npm run certs
To trust the CA in the iOS Simulator, you need to open the folder
~/Library/Application Support/mkcert and drag & drop the file
rootCA.pem into the iOS Simulator. If everything works fine, you should see the root certificate in the settings app under “General > About > Certificate Trust Settings” enabled.
After starting the Express webserver with:
you may open the Xcode workspace file and run the app in the iOS Simulator.
The Example App
There are four buttons below the iframe you can use. If you press “Show Cookies” you will see the values of three cookies. A value of “**UNAVAILABLE**” means that the cookie was not present in the request. I have chosen three different samesite values to ensure, that this is not relevant for the behaviour.
To (try to) add the cookies, you either use the button “Add Cookies” or do a simulated implicit flow with “Start Flow” instead.
No Cookies Available
Regardless of what you do, the iframe cannot set or read any cookies because it makes a “Third-Party” request; thus, ITP is blocking access to any storage. In the case of the simulated flow, we can see that the server is setting the cookies when using the Web Inspector of Safari before returning to the “client app”:
However, in the iframe, they seem to be gone:
Some Possible Non-Conclusive Solutions
Harmonize the Domain
By default, Cordova serves the app on the “localhost” domain. If you change this to the domain used for the flow, the iframe will be a “First-Party” request as the domain matches and has full access to any storage. To change the domain of the app, edit the value of the preference entry in the
In the sample, the requests to the webserver use the hostname “127.0.0.1”; changing the preference to this value makes the ITP happy:
Changing the hostname may be the easiest solution if only one fixed hostname comes in place. But if the hostname is not likely known during compile time or needs to be changed later, it doesn’t accommodate.
(Re-)Enable Cross-Website Tracking
It is possible to enable cross-website tracking in the ITP. Unfortunately, not programmatical. You need to instruct the user to change a setting in the native settings app of iOS explicitly for your app. To make the setting visible, you have to add the key
NSCrossWebsiteTrackingUsageDescription to the
Info.plist with a descriptive value. Currently, the value isn’t visual anywhere.
Now it is possible to switch the ITP into a less aggressive way by allowing cross-website tracking in the settings app:
There are plugins available that are capable of redirecting the user to the settings app. You may try to test in your app if the ITP is active and show a message to the users saying that they will now be redirected and need to allow the tracking.
When allowed, the iframe can read, write, and remove the cookies. However, after removing them, it isn’t capable of creating these again. But in the login scenario, this will be done by the redirected “First-Party” request anyway without problems.
Use a Refresh Token Instead of Silent Refresh
Using a refresh token doesn’t utilize a cookie while getting a new access token. Switching to Authorization Code Flow with PKCE than would be the next decision, instead of adding “offline_access” to the list of scopes of the Implicit Flow only.
With disabling third-party cookies (or storage), the life of applications running in WKWebViews got harder. There are some possibilities to encounter those issues, but in the end, it seems that it will be necessary to get rid of the need for any cookies if the hostname cannot be the same. Depending on the user’s manual settings change isn’t a very user-friendly way to encounter this problem. Also, encouraging the users to do this (otherwise, the app could malfunction or not work at all) may not be an allowed approach when consulting the App Store Review Guidelines in topic “(iv) Access” and can lead to rejection during a review.