Skip to main content
  1. Articles/

Using DeviceCheck to Prevent Abuse and Misuse

<time datetime="2023-02-22 00:00:00 &#43;0000 UTC">2023-02-22</time><span class="px-2 text-primary-500">&middot;</span><span title="Reading time">8 mins</span><span class="px-2 text-primary-500">&middot;</span><span> <span id="views_posts/hg-devicecheck/index.md" title="views">0</span> <span class="inline-block align-text-bottom"> <span class="relative block icon"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"> <path fill="currentColor" d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg> </span> </span> </span><span class="px-2 text-primary-500">&middot;</span><span> <span id="likes_posts/hg-devicecheck/index.md" title="likes">0</span> <span class="inline-block align-text-bottom"> <span class="relative block icon"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path fill="currentColor" d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> </span> </span> </span><span class="px-2 text-primary-500">&middot;</span><span> <button id="likes_button" class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400" onclick="process_article()"> <span id="likes_button_heart" style="display:none" class="inline-block align-text-bottom"> <span class="relative block icon"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path fill="currentColor" d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> </span> </span> <span id="likes_button_emtpty_heart" class="inline-block align-text-bottom"> <span class="relative block icon"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path fill="currentColor" d="M244 84L255.1 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 0 232.4 0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84C243.1 84 244 84.01 244 84L244 84zM255.1 163.9L210.1 117.1C188.4 96.28 157.6 86.4 127.3 91.44C81.55 99.07 48 138.7 48 185.1V190.9C48 219.1 59.71 246.1 80.34 265.3L256 429.3L431.7 265.3C452.3 246.1 464 219.1 464 190.9V185.1C464 138.7 430.4 99.07 384.7 91.44C354.4 86.4 323.6 96.28 301.9 117.1L255.1 163.9z"/></svg> </span> </span> <span id="likes_button_text">&nbsp;Like</span> </button> </span>
Hidden Gems Swift Apple Frameworks IOS Security Devicecheck Swift Ios Apple-Framework
Author
D.B. Galeli
Independent Swift and SwiftUI Developer
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.

Keep in mind: Apple suggests keeping in mind that people might sell or give away the device, to reset states accordingly and to provide users a way to reach out in case they, as new owners, are affected.

How does DeviceCheck work? #

Setting the bits looks like this:

flowchart BT subgraph 1[Local Device] direction LR A("1) DCDevice.current.generateToken") end 1 ===> |"2) Send Token"| 3 subgraph 2[Remote] 3 ===>|"4) Set bits"| D(Apple's DeviceCheck API) subgraph 3[Your Server] F("3) Combine Token w/Authentication Key into JWT") end end subgraph " developer.apple.com/account/resources/authkeys/list " direction TB E(Authentication Key From Apple)==>F end
  1. Generate an ephemeral token in your app, this identifies the device
  2. Send this token to your server
  3. Combine this token with the authentication key you got from Apple to create a JWT with payload
  4. 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:

flowchart BT subgraph 1[Local Device] direction LR A("1) DCDevice.current.generateToken") end 1 ===> |"2) Send Token"| 3 subgraph 2[Remote] 3 ==>|"4) Query bits"| D(Apple's DeviceCheck API) subgraph 3[Your Server] F("3) Combine Token w/Authentication Key into JWT") end end subgraph " developer.apple.com/account/resources/authkeys/list " direction TB E(Authentication Key From Apple)==>F end D===>|"5) Receive bits"|3 3==>|"6) Pass information"|1
  1. Generate an ephemeral token in your app used to identify the device
  2. Send this token to your server
  3. Combine this token with the authentication key you got from Apple to create a JWT with payload
  4. Query for bits by sending the JWT with payload to Apple’s DeviceCheck API endpoint
  5. Receive bits with timestamp from the DeviceCheck API endpoint
  6. 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.

itslazyvar/DeviceCheckExample

Repo with example app and NodeJS server to try out Apple’s DeviceCheck

Swift
0
0

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:

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!



Hidden Gems - This article is part of a series.