This guide will explain how a mobile app proves it is a genuine install with App Attest and exchanges that for client tokens — no shipped secret required. An iOS app with no backend and no user accounts can still call Roadie safely. Apple App Attest proves that a request comes from a genuine, unmodified build of your app on a real device, using a hardware-backed key in the Secure Enclave. The gateway turns that proof into a per-device end user (device:<hash>) and mints a short-lived client token for it. The only credential your app ships is a publishable key, which is mint-only.

How the flow works

Device auth is a three-step handshake against publishable-key-only routes:
  1. POST /v1/device/challenge — the gateway issues a single-use challenge (valid ~5 minutes).
  2. POST /v1/device/attest — on first launch, the app returns its App Attest attestation over the challenge. The gateway verifies it against Apple’s pinned App Attest root, stores the device’s public key and sign counter, and mints a token for the per-device end user.
  3. POST /v1/device/assert — a returning device proves liveness with an assertion (a signature over a fresh challenge plus a strictly increasing counter that defeats replay), and mints again.
Every verification failure — a bad chain, a wrong nonce, a reused challenge, a replayed counter — collapses to one opaque authentication_error, so a probe learns nothing about which check failed. The abuse bound lives on challenge issuance, which is covered by the same per-key and per-IP mint limiter as the backend mint route.

1. Enable device auth for the project

Device auth is off by default and enabled per project. Turn it on and supply your app’s public identity:
  • Apple Team ID — the 10-character team identifier.
  • App bundle id — your app’s reverse-DNS bundle identifier.
  • App Attest environmentproduction or development (which AAGUID a genuine attestation must carry).
None of these are secrets — they are your app’s public identity, and they let the gateway verify that an attestation belongs to your app. Also create a publishable key (rd_pk_…) for the environment; it is the only Roadie credential the app embeds.

2. Wire up RoadieKit on iOS

RoadieKit ships an AppAttestTokenProvider that performs the whole handshake for you — generate/attest the key on first run, assert on later runs, cache the token in memory, and re-mint before expiry or after a 401. Only the App Attest key id is persisted (Keychain); the token never is.
From here your image calls authenticate with the client token the provider mints — you never handle the JWT or the device handshake yourself. Swapping the token provider (for example, to a static token in tests) changes nothing at the call site.
App Attest requires a real device — it does not run in the iOS Simulator or on CI. RoadieKit puts the DCAppAttestService behind a protocol so tests can inject a fake.

3. Or drive the REST flow directly

If you are not using RoadieKit, call the three routes yourself with the publishable key as the bearer. The key_id is your App Attest key id; challenge is the value from step 1; attestation / assertion are base64-encoded. The mint response is { token, expires_at, end_user_id }.
The App Attest tutorial has the full request bodies and Swift plumbing — see App Attest device sign-in.

Anti-farming: the DeviceCheck free-tier guard

An optional Apple DeviceCheck token can travel alongside the attestation (device_check_token). Apple persists two bits per device that survive reinstalls and device erase, so Roadie can stop a device from farming a fresh free tier by reinstalling. It is:
  • Optional and fail-open — an older SDK, an unsupported device, or a token error simply grants the normal free tier and never blocks the mint.
  • Off by default and configured per project (it needs your DeviceCheck signing key).
When a device that has already burned its monthly free allotment re-attests, the guard stamps the reserved free_exhausted tier, which paywalls it until the UTC month rolls over — or until you grant it pro. That is the bridge to Free → Pro with entitlements.

What you get

  • Short-lived, per-device client tokens with no secret shipped in the app.
  • Each device is a distinct end user, so per-user limits and blocking apply per device.
  • A clean upgrade path from an anonymous free device to a paying customer via entitlements.

Client tokens

How device attestation mints a token.

App Attest tutorial

The full device sign-in walkthrough.

Free → Pro entitlements

Turn a free device into a paying user.

iOS SDK — RoadieKit

The Swift SDK reference.