In this Article

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.

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.

Redirect Flow

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.

Silent Refresh

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.

Trust the certificate

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

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

Cookies during flow

However, in the iframe, they seem to be gone:

Cookies after flow No cookies in iframe

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:

Cookies in iframe with matching hostname

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.

Add key to Info.plist

Now it is possible to switch the ITP into a less aggressive way by allowing cross-website tracking in the settings app:

Allow cross-website tracking

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.

If you do not want to miss out on further articles, webinars, and videos by our experts, sign up to our monthly dev news, and we will keep you up to date.

Related Articles

 | Max Schulte

Today not every platform-native API is available to the browser and therefore not available for a web application. That is where additional frameworks like Capacitor and the Ionic Framework take over and close the gap between native applications and web applications. In this…

Read article
 | Max Schulte

Through web APIs, the browser gives you access to those features. Some API parts are easy to use; others require user permission and a secure context. This is where Ionic's Capacitor comes into play. It provides you with unified access to those APIs. But how does Capacitor help…

Read article
 | Max Schulte

The included Ionic animations system is framework-agnostic, and it leverages the Web Animations API to create performant and resource-saving animations. It is available with the Ionic Framework version 5.0 and upward. Version Information: Angular: 9+ Ionic Framework 5.0 Let's…

Read article