
The iOS ecosystem is often regarded as a safer platform among its competitors. Though iOS devices are less likely to be used to perform fraudulent actions, businesses still need to protect themselves from malicious actors.
For iOS, Apple continues to provide several frameworks/tools to prevent fraud. However, since Apple also heavily focuses on user privacy, these tools require app developers to introduce friction in the user journey, which frustrates legit users.
This article gives an overview of the existing techniques and APIs native to iOS that allow developers to harden application security. We also weigh the pros and cons of methods that are provided by Apple and cover an alternative approach that is successfully deployed in our fraud detection solutions on other platforms.
Our approach is especially relevant in use cases that aren’t covered by existing frameworks, such as:
- Preventing unauthorized or excessive account sharing, where the service provider needs to count the number of individual devices tied to a particular account.
- Detecting account takeover attempts to alert users that their account displays abnormal behavior. This can be especially effective if the detection algorithm takes into account previously caught malicious devices.
We will also introduce our newly released iOS library and explain how we implemented the previously mentioned methods. This library is meant to be an open-source showcase and does not fully reflect more advanced capabilities deployed in our Pro version.
Does iOS offer out-of-the-box anti-fraud APIs?
iOS 11 and above include a native fraud protection solution called DeviceCheck. This framework brought an option to flag devices by permanently writing a tiny amount of data to a device. The API permits developers to set and retrieve two bits of information. The interpretation of their meaning is left to the app developer (e.g., the bits could be used to store a flag that determines whether the user completed onboarding, applied a one-time discount coupon, or anything else that could be represented with a true/false value).
This restriction limited the area of possible use cases because it never allowed assigning more data to a particular device. The persistent bits can flag a fraudulent device; however, it does not serve as a full substitute for a unique device identifier.
iOS 14 then added DCAppAttestService with its app validation capabilities. App Attest enables a process that helps developers check the legitimacy of the current application. The legitimacy is assessed through a call to a dedicated third-party service (owned and managed by Apple) that previously generated a unique cryptographic signature used to subsequently identify the application. (The specifics of the process are out of scope of this article but we suggest reading the official documentation for details.)
However, both APIs require network communication with Apple’s servers, making the whole solution dependent on a cloud-based, third-party service.
The code running the verification is also proprietary and closed-source. That might not be a problem for everyone, but it inevitably strips away the option to scrutinize the correctness and security of the whole procedure.
The DeviceCheck
framework shows promise and definitely has its uses. The fact that the two bits set through the API survive factory resets makes it a really distinct and powerful tool in the fraud protection toolchain. On the other hand, it remains very restrictive, which hinders its use in more nuanced cases where two bits of storage isn’t enough.
Other built-in methods for device identification
There are also other methods that can be exploited for device identification but usually have various restrictions and require different levels of user involvement (e.g., displaying a dialog or requiring the user to enable something manually in the settings). To give a few examples:
- The now often-hated Advertising Identifier from the
AdSupport
framework. It became heavily restricted in its underlying usage by requiring user’s cooperation (opt-in in the settings). - Mobile Device Management (or MDMs) have the capability to retrieve the IMEI (International Mobile Equipment Identity) number. The IMEI uniquely identifies a device, but requires the user to cooperate and install a management profile onto their device, which is a multi-step process in itself.
- Location tracking can help identify a device but, again, needs explicit user consent and preferably continuous data gathering to be effective.
It’s inadvisable to try and misuse the restricted APIs because that usually leads to an App Store submission rejection or, in more severe cases, might result in developer account suspension.
These restrictions have made identifying iOS devices without user consent more challenging than ever.
Local device fingerprinting on iOS
The iOS landscape has been steadily and successfully tying up all its loose ends that made it possible to identify users without their explicit consent. Reliably identifying a particular device in the wild should be next to impossible unless the user explicitly provides consent. It should also come with all the ramifications that stem from the use of the underlying tracking mechanism.
However, it still remains possible to collect little pieces of information that are freely available on an iOS device (hardware information, disk and memory size, OS and kernel information, user settings, etc.). At Fingerprint, we call those bits of information signals because each of them is able to convey a particular set of properties related to the observed iPhone or iPad.
Some signals are stable, meaning that their output values rarely or never change. Some are less stable, such as user-managed settings, OS version information, and stored application data.
Besides stability, what we look for in a signal is its uniqueness, in such that no two devices will have the same value for the same signal source.
Therefore, a perfect signal is completely unique and maximizes stability. Examples of a perfect signal can be found among hardware identifiers such as an IMEI number, which aren’t available for application developers through public APIs.
Each signal has a finite and typically limited number of potential output values, and each one is only a small contribution to the final fingerprint value. The combined entropy of the resulting fingerprint is ideally high enough to significantly reduce the search space of possible devices (i.e., the number of devices that have the same fingerprint). Such a method can be then used to generate a fingerprint that fulfills the function of a reliable device identifier.
As mentioned earlier, there is a good argument to be made for purely local device fingerprinting. In an ideal world, the signals would be collected as silently as possible, without any user input and without requiring any additional application capabilities.
Vendor identifier & Keychain storage
The ultimate identification power comes from a combination of different signals and approaches that increase the stability of the final identifier or fingerprint. No signal alone is stable enough to outlive inevitable application reinstalls and system updates. Although there is one iOS-specific signal that gets very close to perfect stability and uniqueness, it lacks an important detail — it changes when the user deletes and installs the host application again.
The near-perfect signal comes from the UIDevice::identifierForVendor
API that always returns a unique ID for a given combination of application vendor and the current device. It might not be clear what exactly that means so let’s demonstrate that in the following scenarios:
- Two applications from the same vendor are installed on the device, with their application identifiers sharing a common prefix
com.mercury.foo
andcom.mercury.bar
, both return the sameidentifierForVendor
value. - Two applications from different vendors with appIDs
com.mars.baz
andcom.mercury.foo
each have their own uniqueidentifierForVendor
that cannot be accessed by an application from different vendors.
identifierForVendor
value has one major drawback. The returned value changes in the extremely likely situation where all applications from a particular vendor (sharing an appId
prefix) have been removed from the device.
Fortunately, there is a way that reduces the impact of this disadvantage and keeps the vendor identifier consistent even after the application is removed. It’s not well known, but data stored in the iOS Keychain from a particular application isn’t wiped along with it when the application gets deleted. The only exception to this rule, for now, is a factory reset (or other manual and intentional data cleanup triggered from the iOS settings).
This behavior allows saving the identifier the first time it’s been generated. Subsequent calls to get the identifier then return the same value saved in the Keychain, significantly increasing the stability of the identifier even between application re-installations.
It’s important to note that the current Keychain behavior that allows the identifier to outlive the application removal is not documented. Despite the fact that there have been official answers indicating possible changes in the future, the functionality has remained untouched for several years. Nevertheless, the recommended approach to undocumented behavior is to have a fail-safe in place, making the identifierForVendor
API an integral part of the entire solution.
Introducing Fingerprint Open-Source for iOS
Our open-source library for iOS collects device signals, and leverages Vendor Identifier and Keychain Storage to generate a stable fingerprint for the device.
Since those two identifier types (device identifier and fingerprint) might have separate use cases, we decided to split them into two different methods that are exposed through the library’s public API: See documentation here.
Fingerprinter::getDeviceId()
Utilizes the vendor identifier and saves it into the Keychain which improves the stability and ensures that reinstalls don’t change the returned value.
Fingerprinter::getFingerprint()
Computes and returns the fingerprint from the available information on the device, currently a combination of hardware and software signals. This part is highly experimental and might not yield very accurate results.
Using Fingerprint Open-Source for iOS
Our open-source library is unobtrusive; it never requests any permissions or capabilities other than what the app already needs for its normal operation. It is also worth mentioning that there aren’t any network calls involved and data is not shared with any third parties.
Fingerprint’s open-source library for iOS doesn’t use any private or restricted APIs, making it well-suited for use with apps that are (or will be) distributed in the iOS App Store. However, always keep in mind that the usage has to conform to the App Store Review Guidelines.
Going beyond with Fingerprint Pro
As we outlined above, our open-source library demonstrates some techniques that we also use in our commercial Fingerprint SDK for iOS. Because we use the Keychain method, our visitorId
remains stable between application reinstalls. Additional signals and backend communication then ensure that it goes way beyond the stability and reliability of local fingerprinting.
Next steps:
- Test out our demo application
- Visit the README and integrate the library in your projects
- Tell your friends about us and recommend us on GitHub
- PS: We’re hiring for several developer roles!