Device integrity is not authorization: an App Attest and Play Integrity decision model
How to separate mobile attestation evidence from identity, request authorization, and server-side replay controls.
A device-integrity verdict can tell a backend something useful about the environment associated with a request. It cannot tell the backend that the caller owns the account or is authorized to modify a particular object. Treating those three questions as one boolean creates a weak trust boundary.
This note is a reference model, not a claim that a particular app or backend shipped this exact implementation. The accompanying interactive device-trust lab uses synthetic flags to demonstrate policy failures; it does not perform cryptographic attestation.
Three different questions
- Integrity: Is the evidence consistent with the expected app instance, distribution context, and device environment?
- Authentication: Which principal established the session, and how was it issued?
- Authorization: Is this principal allowed to perform this action on this resource, right now?
A positive integrity signal does not substitute for either authentication or resource-level authorization. Likewise, a correct password does not excuse a missing object-level access check.
For a sensitive action, a useful server flow is:
- Authenticate the caller and look up the current server-side session state.
- Authorize access to the specific resource and operation.
- If integrity evidence is required, validate it through the platform verification process.
- Bind the evidence to the expected request context and challenge.
- Enforce replay limits and any additional risk policy.
- Log the policy outcome and a correlation ID without logging secrets.
What Apple App Attest provides
Apple's server-side App Attest validation guide describes a two-stage model: first verify an attested key and retain the verified public key, then verify assertions for later requests. The app requests a unique challenge from the server. The backend validates the certificate chain and authenticator fields during key attestation, and validates signed assertions, expected application identifier, request challenge, and counter during subsequent use.
A nonce is not sufficient by itself. The backend needs to bind a one-time challenge to the action it intended to protect, reject reuse, enforce an expiration policy, and avoid racing two acceptance attempts against a read-then-write replay check. For App Attest, assertion counter handling is also part of validation.
The browser lab exposes each of those checks as a checkbox to show the shape of a policy; checking “valid signature” is not signature verification.
What Google Play Integrity provides
For Play Integrity standard requests, the app can include a requestHash derived from the relevant action parameters. The backend sends the integrity token to Google Play for decryption and verification, then checks the returned request details and verdict.
Google recommends checking the returned requestHash against the backend's expected digest. Hashing relevant request fields helps prevent substituting evidence gathered for another request. Request data must be serialized consistently on both sides. Do not put secrets directly in the requestHash value.
Standard requests include replay protections managed by the service. The sample App Attest counter/nonce policy does not map directly to every Play Integrity API mode: the platforms have different evidence models.
A simplified backend decision function
This pseudocode assumes all actual token verification already happened in trusted code. Its booleans are outputs of verification, not client-supplied flags.
A real implementation needs trusted cryptographic verification, server-managed replay state, atomic consumption, request binding, key rotation, error handling, monitoring, and action-specific policy. Never let the client decide the values supplied to this function.
Failure cases worth testing
| Case | Expected behavior |
|---|---|
| A formerly valid proof is replayed | Deny reuse |
| Evidence belongs to a different request | Deny binding mismatch |
| The caller is authenticated but owns a different object | Deny access |
| The signature or attestation chain is invalid | Deny protected action |
| The integrity service temporarily fails | Apply an explicit risk-specific degraded policy |
| The account session was revoked | Deny even if integrity proof is valid |
For low-risk endpoints, degrading gracefully may be reasonable. For privileged actions, an outage must not silently convert “unable to verify” into “verified.”
Try changing each input in the device trust and replay simulator, then consider what would make each result independently verifiable in a production API. Related reading: OWASP API1 — Broken Object Level Authorization.
Editorial note: Educational reference prepared with AI assistance and grounded in vendor documentation, not a production incident report.
Continue exploring
Related technical notes with overlapping topics.