I separated three independent decisions in my mobile trust model: whether the device evidence verifies, which principal made the request, and whether that principal can act on the specific resource. Combining them is how a legitimate integrity verdict becomes an accidental authorization bypass.
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.”
A production verification design needs independently verifiable identity, action-bound evidence, one-time replay checks, and per-resource authorization. Related reading: OWASP API1 — Broken Object Level Authorization.
I prepared this explainer with AI assistance and platform documentation. It isn't a production incident report.
