iOS 14’s Intelligent Tracking Prevention is Considered Harmful: Watch Out Cordova and Ionic Capacitor Developers!

Starting with iOS 14, the Intelligent Tracking Prevention raises some problems in Cordova or Capacitor apps, especially when the OAuth Implicit Flow based on Cookies with Silent Refresh is used for login and authentication.

In this article:

TH-round
Thomas Hilzendegen is consultant at Thinktecture and focuses on large-scale Angular applications and their performance.

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:

  • IndexedDB
  • LocalStorage
  • Media keys
  • SessionStorage
  • 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”:

				
					<preference name="scheme" value="app" />
<preference name="hostname" value="localhost" />
				
			

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:

				
					npm start
				
			

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

				
					<preference name="hostname" value="your.secure-domain.com" />
				
			

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.

Conclusion

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.

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
AI
sg
One of the more pragmatic ways to get going on the current AI hype, and to get some value out of it, is by leveraging semantic search. This is, in itself, a relatively simple concept: You have a bunch of documents and want to find the correct one based on a given query. The semantic part now allows you to find the correct document based on the meaning of its contents, in contrast to simply finding words or parts of words in it like we usually do with lexical search. In our last projects, we gathered some experience with search bots, and with this article, I'd love to share our insights with you.
17.05.2024
Angular
SL-rund
If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
15.04.2024
.NET
KP-round
.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023