Using DeviceCheck to Prevent Abuse and Misuse
Table of Contents
Hidden Gems - This article is part of a series.
Hidden Gems #
Due to the sheer amount of announcements Apple makes at WWDC nowadays, many useful frameworks are vying for a place in the spotlights. As such, some will inevitably escape the notice of developers.
Hidden Gems is a new series that aims to draw attention to changes and additions of new frameworks that might be of interest to developers.
In this first installment we’ll be looking at Apple’s DeviceCheck service.
What is DeviceCheck? #
Apple announced DeviceCheck at WWDC17. In essence, this service allows developers to store 2 bits of data on Apple’s servers. In addition to this data, a timestamp of the date of last modification is stored.
This data persists on Apple’s servers, regardless of what happens to your app on the user’s device.
Limitations of DeviceCheck #
In order to be able to explain the benefits of using DeviceCheck and its potential use cases, we first need to take a look at the limitations of DeviceCheck.
- Developers get 2 bits of data and a timestamp per device, per developer
- These 2 bits translate to 4 possible states, see table below
- The limitation per developer means that if you have multiple apps that you want to use DeviceCheck with, you’ll have to share these 2 bits amongst your apps
- Any logic you have that relies on these states needs to take into account that these states are bound to the device, not the user
- This means that a user can have two devices running your app and these devices can return two different states
- This also means that if a user sells or gives away their device, the state you last set will travel with the device
- You need to setup a server to be able to use the DeviceCheck service (don’t worry though, I’ll provide a relatively easy example to use)
- The developer is responsible for managing these 4 states
- This means that the developer needs to keep track of what these 4 states mean
- This also means that the developer needs to consider cleaning up the states, if appropriate
bit0 | bit1 |
---|---|
false | false |
false | true |
true | false |
true | true |
So what is DeviceCheck good for? #
At first blush, these limitations might make it hard to see how DeviceCheck can be useful, but hopefully the examples below help illustrate how powerful DeviceCheck can be.
Free Trial #
IAP purchase flows offer the option to provide the user with a trial period, but users can be quite apprehensive towards IAPs out of fear that they’ll be charged. With this in mind, you could consider providing new users with temporary access to your premium content as a form of trial.
However, the risk of implementing this is that users might abuse this by deleting and reinstalling your app each time the trial ends, and in the process gain unlimited access to your premium content.
DeviceCheck can be used in this instance to keep track of if a device has already been given access to the trial in the past.
Banning Problematic Users #
Perhaps you’re working on a social media app or a dating app, as part of supporting the app, banning problematic users will be necessary to protect the quality of the experience for the rest of the user base.
Inevitably, a subset of these banned users will try and evade the ban by creating a new account or by using an alternative account they had already created prior.
With DeviceCheck you could make it harder for problematic users to evade their ban by essentially tagging their device. You can then use the knowledge that the device has been used by a banned user in your logic for dealing with banned users or even take a hardline stance and ban the device itself.
Alternatively, you can also use DeviceCheck to pro-actively rate limit account creation to n
account(s) per device and prevent ban evasion with alt-accounts in the future this way.
Restoring Purchases #
Users often appreciate it when your app acknowledges prior IAPs during initial launch by unlocking everything tied to that IAP.
A lot of developers accomplish this by trying to restore purchases during initial launch, but Apple warns against restore attempts that are not initiated by a user action because under certain circumstances it can trigger a login prompt that asks the user to login with their Apple ID. This login prompt can confuse users and might even lead them to think that your app is attempting to collect their login credentials.
Instead, let DeviceCheck keep track of prior purchases by using the states to keep track if a purchase has occurred. That way you can check during initial launch if a purchase has taken place and show the user an alert in which you offer them to restore their purchases.
Lightweight and powerful #
I hope that the examples above were able to illustrate how powerful DeviceCheck can be and that it might’ve sparked some new ideas of your own.
DeviceCheck is designed with privacy in mind. You can achieve the goals described in the examples above while respecting the user’s privacy. You don’t have to collect user data nor do you have to deal with device identifiers, DeviceCheck can be used without storing any of that information on your own server.
Some of you reading this might by now wonder why you should bother with DeviceCheck and not just use Keychain to store this information and the answer to that is that persistance across installs is not part of the Keychain specs according to Apple.
In fact, they played around with the idea of deleting Keychain items upon deletion of the app, but reversed course when it caused issues because so many apps relied on persistance. Since persistence is undocumented behavior for Keychain and there are possible plans in the future to delete Keychain items upon app deletion, Apple advises to use DeviceCheck instead.
How does DeviceCheck work? #
Setting the bits looks like this:
- Generate an ephemeral token in your app, this identifies the device
- Send this token to your server
- Combine this token with the authentication key you got from Apple to create a JWT with payload
- Set bits by sending the JWT with the payload to Apple’s DeviceCheck API endpoint
Querying for bits looks similar, it just has additional steps at the end:
- Generate an ephemeral token in your app used to identify the device
- Send this token to your server
- Combine this token with the authentication key you got from Apple to create a JWT with payload
- Query for bits by sending the JWT with payload to Apple’s DeviceCheck API endpoint
- Receive bits with timestamp from the DeviceCheck API endpoint
- Pass along this information to your app
Final thoughts and Example Code #
If you’d like to give DeviceCheck a try yourself you can find a repository with an example app below. This repository also includes an example NodeJS server to get you up and running.
Repo with example app and NodeJS server to try out Apple’s DeviceCheck
Apple also provides documentation on DeviceCheck here, with example payloads here.
The original session ("Privacy and Your Apps", Session 702, WWDC17) in which they announced and explained DeviceCheck has been unlisted from Apple’s website for some reason, but it it’s still being hosted on their servers here (relevant part starts around the 24m mark).
Since launching the DeviceCheck service, Apple has expanded on it with App Attest. App Attest is a framework that, amongst other things, allows you to check if your app is compromised. Perhaps an interesting topic to cover in a future article.
Apple has published two sessions during WWDC21 that cover DeviceCheck, App Attest and other frameworks:
- "Safeguard your accounts, promotions, and content", Session 10110, WWDC21
- "Mitigate fraud with App Attest and DeviceCheck", Session 10244, WWDC21
Lastly a huge thanks to Tim Colla over at Marino Software who’se blog post made me aware of the existence of DeviceCheck 5 years ago and whose generously shared code got me up and running back then.
I hope I was able to pass on the knowledge and generosity with this blog post.
Feel free to reach out to me on Mastodon should you have any questions, feedback or comments or if you’d just like to share your examples of how DeviceCheck could be useful.
Have fun playing around with DeviceCheck and thanks for reading!