Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
90
90%
Does it follow best practices?
Impact
—
Average score across 248 eval scenarios
Advisory
Suggest reviewing before use
Overflow reference for the device-integrity skill. Contains server verification details, advanced error handling, and integration patterns.
Your server must:
clientDataHash = SHA256(challenge), append it to the decoded
authData, then compute nonce = SHA256(authData || clientDataHash).1.2.840.113635.100.8.2 and verify its octet string equals nonce.keyId.RP ID hash matches SHA256(teamID + "." + bundleID).0, the aaguid matches the expected
development or production environment, and credentialId equals keyId.See Validating apps that connect to your server for the full server verification algorithm.
Your server must:
clientDataHash = SHA256(clientData), where clientData
includes a one-time server challenge and request context.SHA256(authenticatorData || clientDataHash).RP ID hash and the counter (greater than the stored counter, or
greater than 0 for the first assertion).| Phase | When | What It Proves | Frequency |
|---|---|---|---|
| Attestation | After key generation | The key lives on a genuine Apple device running a legitimate instance of your app | Once per key |
| Assertion | With each sensitive request | The request came from the attested app instance | Per request |
keyId.Reject expired, missing, mismatched, or already-consumed challenges. Consume a challenge only after the corresponding attestation or assertion is fully verified; consuming on receipt can block safe retries after transient failures.
Combine App Attest with fraud risk assessment for defense in depth. App Attest alone does not guarantee the user is not abusing the app -- it confirms the app is genuine.
App Attest is not a user authentication, session, entitlement, TLS, certificate pinning, or subscription validation system. Keep those controls in the appropriate authentication, networking, or broader security layer, and require them in addition to App Attest on protected endpoints.
import DeviceCheck
func handleAttestError(_ error: Error) {
if let dcError = error as? DCError {
switch dcError.code {
case .unknownSystemFailure:
// Transient system error -- retry with exponential backoff
break
case .featureUnsupported:
// Device or OS does not support this feature
// Fall back to alternative verification
break
case .invalidKey:
// Already-attested key, unattested assertion key, or service rejection
// Inspect local/server state; discard and regenerate only when bad
break
case .invalidInput:
// The clientDataHash or keyId was malformed
break
case .serverUnavailable:
// Retry attestation later with the same keyId and clientDataHash
break
@unknown default:
break
}
}
}import CryptoKit
extension AppAttestManager {
func attestKeyWithRetry(challenge: Data, maxAttempts: Int = 3) async throws -> Data {
guard let keyId else {
throw DeviceIntegrityError.keyNotGenerated
}
let clientDataHash = Data(SHA256.hash(data: challenge))
var lastError: Error?
for attempt in 0..<maxAttempts {
do {
return try await service.attestKey(keyId, clientDataHash: clientDataHash)
} catch let error as DCError where error.code == .serverUnavailable {
lastError = error
if attempt < maxAttempts - 1 {
try await Task.sleep(for: .seconds(pow(2.0, Double(attempt + 1))))
}
} catch {
throw error // Non-retryable errors propagate immediately
}
}
throw lastError ?? DeviceIntegrityError.attestationFailed
}
}Use the same challenge, keyId, and clientDataHash for each retry after
.serverUnavailable. Do not fetch a fresh challenge for that retry loop unless
you are also starting over with a new attestation attempt.
DCError.invalidKey means the app called attestKey for an already-attested
key, called generateAssertion with an unattested key, or the App Attest service
rejected the key. If local/server state confirms the key cannot be used, delete
the stored keyId and generate a new key:
extension AppAttestManager {
func handleRejectedKey() async throws -> String {
deleteKeyIdFromKeychain()
keyId = nil
return try await generateKeyIfNeeded()
}
private func deleteKeyIdFromKeychain() {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "app-attest-key-id",
kSecAttrService as String: Bundle.main.bundleIdentifier ?? ""
]
SecItemDelete(query as CFDictionary)
}
}Combine the patterns above into a single actor that manages the full lifecycle:
isSupported and fall back to DCDevice tokens on unsupported devices.generateKeyIfNeeded() for each user account on each device, reuse the
account/device-scoped keyId, and limit new key generation to new
account/device/install enrollment or confirmed bad-key recovery..serverUnavailable occurs, retry with the same
challenge, key, and clientDataHash.DCError.invalidKey by checking whether the key was already attested,
not yet attested, or rejected before regenerating.Apple recommends a gradual rollout. Gate App Attest behind a remote feature
flag and fall back to DCDevice tokens on unsupported devices. For large apps,
ramp production adoption gradually and be prepared to reduce attestation traffic
if .serverUnavailable or rate-limit behavior increases during rollout.
Set the App Attest environment in your entitlements file. Use development
during testing and production for App Store builds:
<key>com.apple.developer.devicecheck.appattest-environment</key>
<string>production</string>When the entitlement is omitted during development, the app uses the App Attest sandbox by default. After distribution through TestFlight, the App Store, or the Apple Developer Enterprise Program, the app ignores the entitlement value and uses production. Sandbox keys and receipts do not work in production, and production keys and receipts do not work in sandbox.
If an App Clip or extension uses App Attest, configure the capability for that
target too. App Attest is supported only in Action, extensible SSO, and watchOS
extensions; other extension types are unsupported even if isSupported returns
true.
enum DeviceIntegrityError: Error {
case deviceCheckUnsupported
case keyNotGenerated
case attestationFailed
case attestationVerificationFailed
case assertionFailed
case serverVerificationFailed
}.tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references