$npx -y skills add dpearson2699/swift-ios-skills --skill device-integrityVerify device legitimacy and app integrity using DeviceCheck (DCDevice per-device bits) and App Attest (DCAppAttestService key generation, attestation, and assertion flows). Use when implementing fraud prevention, detecting compromised devices, validating app authenticity with Ap
| 1 | # Device Integrity |
| 2 | |
| 3 | Verify that requests to your server come from a genuine Apple device running a |
| 4 | legitimate instance of your app. DeviceCheck provides per-device bits for |
| 5 | simple flags (e.g., "claimed promo offer"). App Attest uses Secure Enclave keys |
| 6 | and Apple attestation to cryptographically prove app legitimacy on sensitive |
| 7 | requests. |
| 8 | |
| 9 | ## Contents |
| 10 | |
| 11 | - [DCDevice (DeviceCheck Tokens)](#dcdevice-devicecheck-tokens) |
| 12 | - [DCAppAttestService (App Attest)](#dcappattestservice-app-attest) |
| 13 | - [App Attest Key Generation](#app-attest-key-generation) |
| 14 | - [App Attest Attestation Flow](#app-attest-attestation-flow) |
| 15 | - [App Attest Assertion Flow](#app-attest-assertion-flow) |
| 16 | - [Server Verification Guidance](#server-verification-guidance) |
| 17 | - [Error Handling](#error-handling) |
| 18 | - [Common Patterns](#common-patterns) |
| 19 | - [Common Mistakes](#common-mistakes) |
| 20 | - [Review Checklist](#review-checklist) |
| 21 | - [References](#references) |
| 22 | |
| 23 | ## DCDevice (DeviceCheck Tokens) |
| 24 | |
| 25 | [`DCDevice`](https://sosumi.ai/documentation/devicecheck/dcdevice) generates a |
| 26 | unique, ephemeral token that identifies a device. Treat each token as |
| 27 | single-use: generate a new token for each server operation instead of caching or |
| 28 | reusing one. The token is sent to your server, which then communicates with |
| 29 | Apple's servers to read or set two per-device bits. Available on iOS 11+. |
| 30 | |
| 31 | ### Token Generation |
| 32 | |
| 33 | ```swift |
| 34 | import DeviceCheck |
| 35 | |
| 36 | func generateDeviceToken() async throws -> Data { |
| 37 | guard DCDevice.current.isSupported else { |
| 38 | throw DeviceIntegrityError.deviceCheckUnsupported |
| 39 | } |
| 40 | |
| 41 | return try await DCDevice.current.generateToken() |
| 42 | } |
| 43 | ``` |
| 44 | |
| 45 | ### Sending the Token to Your Server |
| 46 | |
| 47 | ```swift |
| 48 | func sendTokenToServer(_ token: Data) async throws { |
| 49 | let tokenString = token.base64EncodedString() |
| 50 | |
| 51 | var request = URLRequest(url: serverURL.appending(path: "verify-device")) |
| 52 | request.httpMethod = "POST" |
| 53 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") |
| 54 | request.httpBody = try JSONEncoder().encode(["device_token": tokenString]) |
| 55 | |
| 56 | let (_, response) = try await URLSession.shared.data(for: request) |
| 57 | guard let httpResponse = response as? HTTPURLResponse, |
| 58 | httpResponse.statusCode == 200 else { |
| 59 | throw DeviceIntegrityError.serverVerificationFailed |
| 60 | } |
| 61 | } |
| 62 | ``` |
| 63 | |
| 64 | ### Server-Side Overview |
| 65 | |
| 66 | The server exchanges each fresh token with Apple's authenticated DeviceCheck API. |
| 67 | Load [DeviceCheck Server Endpoints](references/device-integrity-patterns.md#devicecheck-server-endpoints) |
| 68 | for endpoint and environment details. |
| 69 | |
| 70 | ### What the Two Bits Are For |
| 71 | |
| 72 | Apple stores two Boolean values per device per developer team. You decide what |
| 73 | they mean. Common uses: |
| 74 | |
| 75 | - **Bit 0:** Device has claimed a promotional offer. |
| 76 | - **Bit 1:** Device has been flagged for fraud. |
| 77 | |
| 78 | Bits persist across app reinstall. You control when to reset them via the |
| 79 | server API. |
| 80 | |
| 81 | ## DCAppAttestService (App Attest) |
| 82 | |
| 83 | [`DCAppAttestService`](https://sosumi.ai/documentation/devicecheck/dcappattestservice) |
| 84 | validates that a specific instance of your app on a specific device is |
| 85 | legitimate. It uses a hardware-backed key in the Secure Enclave to create |
| 86 | cryptographic attestations and assertions. Available on iOS 14+. |
| 87 | |
| 88 | The flow has three phases: |
| 89 | 1. **Key generation** -- create a key pair in the Secure Enclave. |
| 90 | 2. **Attestation** -- Apple certifies the key belongs to a genuine Apple device running your app. |
| 91 | 3. **Assertion** -- sign server requests with the attested key to prove ongoing legitimacy. |
| 92 | |
| 93 | ### Checking Support |
| 94 | |
| 95 | ```swift |
| 96 | import DeviceCheck |
| 97 | |
| 98 | let attestService = DCAppAttestService.shared |
| 99 | |
| 100 | guard attestService.isSupported else { |
| 101 | // Fall back to DCDevice token or other risk assessment. |
| 102 | // App Attest is not available on simulators or all device models. |
| 103 | return |
| 104 | } |
| 105 | ``` |
| 106 | |
| 107 | For app extensions, App Attest is supported only in Action, extensible SSO, and |
| 108 | watchOS extensions. Treat other extension types as unsupported even if |
| 109 | `isSupported` returns `true`. |
| 110 | |
| 111 | ## App Attest Key Generation |
| 112 | |
| 113 | Generate one cryptographic key pair per user account on each device. The |
| 114 | private key stays in the Secure Enclave. The returned `keyId` is the only |
| 115 | identifier your app can later use to access the key, so record and reuse the |
| 116 | account/device-scoped `keyId`; do not share one key across users. Avoid |
| 117 | unnecessary regeneration because each new key affects App Attest key-count risk |
| 118 | metrics. Only treat the `keyId` as usable after your server verifies |
| 119 | attestation. If server verification fails, discard the `keyId` and generate a |
| 120 | new key before retrying. |
| 121 | |
| 122 | ```swift |
| 123 | import DeviceCheck |
| 124 | |
| 125 | acto |