Device Integrity
Verify that requests to your server originate from genuine Apple devices running legitimate instances of your app. DCDevice provides per-device bits for persistent state (e.g., promo redemption), while DCAppAttestService provides cryptographic proof of app authenticity using Secure Enclave keys.
Security Boundary: App Attest proves client hardware/app integrity; it does not replace user authentication, TLS, authorization, or certificate pinning. Always enforce user authentication after App Attest verification succeeds.
Contents
- DCDevice (DeviceCheck)
- DCAppAttestService (App Attest)
- Key Generation & Attestation Flow
- Assertion Flow
- Error Handling & Key Lifecycle
- Common Mistakes
- Review Checklist
- References
DCDevice (DeviceCheck)
DCDevice generates ephemeral, single-use tokens sent to your backend, which communicates with Apple to read or update two per-device bits that persist across app reinstallation.
import DeviceCheck
func generateDeviceToken() async throws -> Data {
guard DCDevice.current.isSupported else {
throw DeviceIntegrityError.deviceCheckUnsupported
}
return try await DCDevice.current.generateToken()
}
Treat each token as strictly single-use. Generate a fresh token for every server interaction.
DCAppAttestService (App Attest)
Available on iOS 14+. Validates app-instance integrity through three stages:
- Key Generation: Create a hardware-backed key in the Secure Enclave once per user account/device.
- Attestation: Apple certifies that the key belongs to a genuine app instance.
- Assertion: Sign sensitive API requests using the attested key.
let service = DCAppAttestService.shared
guard service.isSupported else { throw DeviceIntegrityError.unsupported }
Key Generation & Attestation Flow
Generate a key and request attestation from Apple before sending it to your backend:
// 1. Generate key once and persist keyId
let keyId = try await service.generateKey()
// 2. Fetch one-time challenge from server
let challenge = try await fetchAttestationChallenge()
let clientDataHash = Data(SHA256.hash(data: challenge))
// 3. Attest key with Apple and send to server
let attestation = try await service.attestKey(keyId, clientDataHash: clientDataHash)
try await sendAttestationToServer(keyId: keyId, attestation: attestation)
The server verifies the CBOR attestation object against Apple's App Attest root CA, checking that the certificate extension nonce matches SHA256(authData || SHA256(challenge)).
Assertion Flow
Sign request payloads and server challenges with the attested key to prove ongoing legitimacy:
// clientData contains one-time challenge + request context
let clientDataHash = Data(SHA256.hash(data: clientData))
let assertion = try await service.generateAssertion(keyId, clientDataHash: clientDataHash)
var request = URLRequest(url: endpointURL)
request.setValue(assertion.base64EncodedString(), forHTTPHeaderField: "X-App-Attest-Assertion")
request.setValue(clientData.base64EncodedString(), forHTTPHeaderField: "X-App-Attest-Client-Data")
The server recomputes the hash, validates the assertion signature with the stored public key, and verifies the counter strictly increases to prevent replay attacks.
Error Handling & Key Lifecycle
Handle DCError codes gracefully:
.serverUnavailable: Retry attestation with the samekeyIdand the sameclientDataHashusing exponential backoff..invalidKey: Discard the corrupted/rejectedkeyIdand generate a fresh key before retrying..featureUnsupported: Fall back toDCDevicetoken validation or server-side risk scoring.
Common Mistakes
- Generating a new key on every launch: Generate once per user account on a device and persist the
keyId. - Reusing DCDevice tokens: Tokens are single-use; cached tokens will fail server validation.
- Signing only the raw request body: Assertion client data must include a one-time server challenge and request context for replay resistance.
- Verifying the wrong attestation nonce on server: Nonce must equal
SHA256(authData || SHA256(challenge)), notSHA256(challenge)alone. - Mixing development and production environments: Development keys fail in production. Configure
com.apple.developer.devicecheck.appattest-environmententitlement appropriately. - Trusting attestation client-side: Verification must strictly occur on your secure backend.
Review Checklist
-
DCAppAttestService.isSupportedverified with fallback for unsupported devices/extensions - Key generated once per account/device and
keyIdpersisted in Keychain - Server verifies attestation certificate chain, RP ID, counter, and composite nonce
- Assertions bind request context to a one-time challenge with monotonic counter validation
-
.serverUnavailableretried with identical key/hash;.invalidKeyregenerates key - Protected endpoints still enforce normal user authentication and TLS
- Entitlements match target environment (
developmentvsproduction)
References
- Server verification algorithms, retry strategies, and integration manager: references/device-integrity-patterns.md
- DeviceCheck framework
- DCDevice
- DCAppAttestService
- Establishing your app's integrity
- Validating apps that connect to your server
- Attestation Object Validation Guide
- App Attest Environment Entitlement