Skip to main content

Validate a device

This performs the full attestation flow:
  1. Requests a one-time challenge from the server
  2. Generates or retrieves an attestation key via Apple’s App Attest
  3. Creates an attestation object on-device
  4. Sends it to the Grantiva server for validation
  5. Returns a signed JWT with device intelligence

AttestationResult

DeviceIntelligence

RiskCategory has three cases and is present on every plan: An unrecognized category string from the server decodes as .trusted.

Token caching

The SDK caches the JWT in the Keychain. Calling validateAttestation() again returns the cached token until it is within 5 minutes of expiry, at which point the SDK gets a new one. Renewal does not re-run the full attest flow. Once a key has been attested, the SDK refreshes by generating an App Attest assertion against a fresh challenge — App Attest permits only one attestation per key over its lifetime. If Apple or the server rejects that assertion (assertionKeyInvalid, reattestRequired), the SDK clears the local key state and runs one full attestation with a new key.

Refresh tokens

Check if the current token is still valid, and refresh if expired:
Returns nil if no token has been stored yet — call validateAttestation() first.
When the stored token is still valid, refreshToken() returns it without contacting the server, and the deviceIntelligence on that result is a placeholder (riskScore is nil, deviceIntegrity is "valid"). Read device intelligence from the validateAttestation() result, not from refreshToken().

Check token status

Clear stored data

Force a fresh attestation on the next call:
This clears cached keys and tokens from the Keychain and stops the SDK’s background work — the usage heartbeat and the flag update stream. Useful for testing or user logout.

Background activity

A successful attestation starts two background tasks:
  • Usage heartbeat — a fire-and-forget ping every 120 seconds while the app is running, used for Monthly Active Device counting.
  • Flag streaming — a Server-Sent Events connection for live feature flag updates. On iOS the SDK stops it when the app backgrounds and restarts it on foreground.
Both stop when you call clearStoredData().

Use the token

Send the JWT to your backend as a Bearer token:
Your backend can decode the JWT to read:
  • Device risk score
  • Jailbreak detection status
  • Device model and OS version
  • Custom claims configured in the dashboard

Challenges

Every attestation starts with a one-time challenge from the server. The SDK requests it automatically, but understanding the rules helps when debugging:
  • Challenges are valid for 5 minutes. A slow attestation flow (e.g. paused in the debugger) can outlive its challenge — the server returns challenge_expired and the SDK requests a fresh one.
  • Challenges are single-use. Replaying a challenge returns challenge_already_used. Never cache or share challenges between attestation attempts.
  • The clientDataHash sent to Apple must be the SHA256 of the exact challenge string. A mismatch surfaces as client_data_hash_invalid.

Troubleshooting

Common causes when the server rejects an attestation (apple_validation_failed):
  1. Bundle ID / Team ID mismatch — the values in the request don’t match what App Attest signed against. Check that the SDK’s teamId matches your Apple Developer Team ID and the build’s bundle identifier is registered in the dashboard.
  2. Running in the Simulator — App Attest only works on real hardware. Use API key mode for Simulator and CI builds.
  3. Reused or tampered attestation object — attestation objects are single-use and bound to their challenge.
  4. Key state drift (reattest_required, HTTP 409) — the device’s stored App Attest key no longer matches the server’s records (backup restore, device transfer). The SDK self-heals by clearing local key state and re-attesting; you’ll only see the error if that retry also fails.
  5. MAD limit reached (HTTP 402) — new device onboarding is paused because your plan’s Monthly Active Device limit was exceeded and the grace period ended. See Billing.

Error handling

See Error Handling for the full list of error types.

Backend verification

See the Backend JWT Verification guide for examples of verifying the token server-side in Node.js, Python, and Go, including JWKS fetching and risk-based access control.