What App Attest Verifies and What It Does Not
The boundary verified by App Attest, short-lived tokens, and request proofs—and the attacks left outside it
Hangangjari provides parking, events, weather, and notifications without user accounts. In place of user authentication, the server needs a signal that an HTTP request started from a registered iOS app instance. API addresses and payload formats can be observed, and an API key embedded in the app can be extracted.
Hangangjari registers app-instance keys with App Attest. Assertions are used only for requests that can change state or operational metrics.
General requests carry an app-instance signal. Requests that change server state or operational metrics add body-integrity verification.
Attack Surface Remaining After App Attest
After App Attest is applied, attackers can still observe public APIs and build automated callers. The server uses the following signals in risk assessment and request-authorization policy.
- Verify that the request started from a distributed app instance.
- Verify continuity with the key and verification material registered by the server.
- Verify the core content of state-changing requests.
- Grant fallback signals only limited authority.
The first-screen API can still be called repeatedly from outside the app. The server distinguishes calls without an app token from registered app requests and applies rate limits and abuse metrics. Requests such as notification-setting changes create server state, so they verify both the app-instance signal and the request body.
App Instance Registration
A security module in the iOS app checks App Attest availability and, when supported, creates a key on the device.
The app does not receive the private key directly. It receives only a key identifier, and stores it in secure device storage only after registration succeeds.
The registration flow starts with a server challenge.
sequenceDiagram autonumber participant App as iOS app participant Apple as Apple App Attest participant API as Hangangjari API participant DB as App-instance store App->>API: Request registration challenge API-->>App: One-time challenge App->>Apple: Create key attestation Apple-->>App: Attestation object App->>API: Register key ID and attestation object API->>API: Verify attestation API->>DB: Store app-instance verification material API-->>App: Short-lived app access token
The server verifies the Apple App Attestation certificate chain, server-issued challenge, app identity context such as the App ID prefix and Bundle ID, and key identifier, then stores the public key and counter used for later assertions.
Short-Lived Tokens for Ordinary Requests
After registration, the iOS app does not repeat attestation for every request. It uses the registered key to refresh a short-lived app access token and includes that token in a header on general API requests.
flowchart LR Request["General app API request"] --> Header["App access token header"] Header --> Verify["Verify signature and expiry"] Verify --> Instance["Look up app instance"] Instance --> Context["Check registration context and state"] Context --> API["Handle API request"]
This is not a user authentication token. The server checks the registered app instance and token state before authorizing general API requests.
After verifying the token, the server checks the app instance in DB again. If the token’s app instance does not match the stored registration context, or if the instance is inactive, the request is rejected.
Binding the Body of State-Changing Requests
Write requests that create state or can pollute operational metrics carry request proof signed with the registered key.
| Request category | Why it needs stronger handling |
|---|---|
| Notification-setting changes | They create or clean up notification settings on the server. |
| Telemetry collection | They affect events and performance signals used as operational metrics. |
The token verifies a registered app instance and expiry. Request proof verifies an assertion over the server challenge and request-body summary.
The server and app combine a one-time challenge with a summary of the request body to create the proof challenge. The app signs it as an App Attest assertion, and the server verifies it with the stored public key. A modified body no longer matches the assertion’s client data. The assertion counter must also exceed the previously stored value, causing captured request replays to be rejected.
Restricted Authority for Fallback
App Attest is not available in every iOS execution environment. Development environments, some runtime environments, and extension contexts can differ.
The Hangangjari iOS app separates development policy from distribution-build policy.
During development, local checks need a way not to block iteration. In distribution builds, App Attest failure does not silently pass.
DeviceCheck fallback also exists. If App Attest is impossible and DeviceCheck is available, the app can receive an app access token with a limited fallback signal.
The server stores fallback tokens with different authority from App Attest tokens. They allow general reads but cannot be used for requests that require request proof.
flowchart TD
Signal["App-origin signal"] --> Strong{"App Attest available?"}
Strong -->|"Yes"| AppAttest["General requests + strong request verification"]
Strong -->|"No"| Fallback["Limited fallback"]
AppAttest --> Allow["Allow according to request type"]
Fallback --> Limited["Allow only limited general app requests"]
Protection by Request Type
Hangangjari records what request verification changes after App Attest and which protections remain necessary.
| Situation | What changes after App Attest | Still needed |
|---|---|---|
| Calls that mimic API shape outside the app | Without an app-origin signal, they are not treated as registered app requests. | IP rate limits, abuse metrics, cache protection |
| Attempt to modify state-changing body | The core request content is verified together. | Notification-setting protection, subscription cleanup, audit |
| Replay of an old request | One-time challenges and a counter greater than the stored value are verified. | Clock handling, storage incidents, retry policy |
| Environment without App Attest | Fallback is treated as limited authority. | Fail-closed policy for distribution builds, development-token management |
| Compromised device or broker-style calls | Fraud metrics provide an additional risk signal. | Rate limits and abuse monitoring |
Apple states that App Attest alone cannot definitively identify a compromised operating system. Hangangjari uses the result as an app-origin risk signal alongside rate limits, log masking, token expiry, key recovery, and operational metrics.
App Attest Fraud Metrics
Apple lets the server send App Attest receipts back to Apple and receive a fraud metric: an approximate count of attested keys created for a device/app pair in the last 30 days.
Hangangjari classifies this as a metric for baselines and abrupt changes rather than an immediate blocking condition.
Reinstallation, backup restoration, and a change in device ownership can also increase the key count, so this metric alone does not block a request.
Verification Policy by Request Category
Each request policy states whether app-instance verification is required, whether the body needs an assertion, which fallback authority is allowed, and what happens after verification failure. General API requests use short-lived tokens, while requests that change state or operational metrics add request proof. DeviceCheck fallback is limited to general reads, giving each of the three request categories different authority.
The Apple documents I referenced were DeviceCheck, DCAppAttestService, Validating apps that connect to your server, and Assessing fraud risk.
Comments
No comments yet. Be the first to leave one.
Pending review