Security documentation
Hardware backed security and attestation
Attestation is a way for a server to ask for evidence about the app, the device, or a cryptographic key before trusting a request. The important part is that the decision is made by a trusted server, not by the same client that might be modified.
TEE
A Trusted Execution Environment AKA "TEE", is an isolated execution area protected from normal Android. It can keep cryptographic keys and security services away from ordinary app processes.
StrongBox
StrongBox is a stronger Android hardware security option backed by a dedicated secure element or integrated secure enclave when the device supports it.
Android key attestation
Android can return an X.509 certificate chain describing a generated key and security properties reported by KeyMint.
Meta Quest attestation
Meta can issue a signed integrity token that your server verifies with Meta to evaluate the Quest app and device state.
Client code collects evidence. The server verifies evidence. The server decides what access the client gets.
Trust model
A useful security design starts by deciding what you trust. Normal application code should usually be treated as untrusted because it runs on a device controlled by the user. A modified app can patch checks, fake return values, remove code, change network requests, or call your backend directly.
Hardware backed security moves some operations into an isolated security boundary. Remote attestation goes one step further by giving your server signed evidence that it can verify independently.
Client
Requests a challenge, asks the platform for evidence, and sends the result to your server.
Platform security
Protects keys or generates signed integrity information using hardware or a trusted platform service.
Server
Checks signatures, freshness, identity, expected app values, replay state, and policy before granting access.
What should never be trusted by itself
A package name sent as normal JSON is only a string. A device model sent by the client is only a string. A SHA256 value calculated by the client is only a string. A boolean such as rooted equals false is only a client claim. Those values become useful only when they are tied to evidence your server can authenticate.
Security layers
TEE, StrongBox, Android key attestation, Meta attestation, user verification, entitlement, and your backend all solve different problems. The easiest way to understand them is to ask what each layer can actually prove.
| Layer | Main job | What it does not prove by itself |
|---|---|---|
| TEE | Protects sensitive code and cryptographic key operations away from normal Android. | It does not prove your APK came from the Meta Horizon Store. |
| StrongBox | Protects supported keys inside more isolated dedicated secure hardware. | It does not automatically prove the rest of the app is unmodified. |
| Android key attestation | Lets a remote server verify properties of a generated key and selected device security state. | It is not a full replacement for Meta application integrity on Quest. |
| Meta application attestation | Lets your backend evaluate app recognition, signing information, device integrity, freshness, and other Meta supplied claims. | It does not prove the player is allowed to perform every backend action. |
| Meta user verification | Helps your backend confirm the Meta user proof for the user identifier being presented. | It does not prove device integrity or application integrity. |
| Entitlement | Answers whether the account has access to the application. | It does not prove the APK or device is clean. |
| Your backend session | Turns trusted verification results into access to your own services. | It is only as strong as the checks used when the session was created. |
Hardware protects keys. Attestation reports evidence. Meta verifies Meta platform signals. Your server decides what the player is allowed to do.
Trusted Execution Environment
A Trusted Execution Environment is an isolated environment that runs alongside the normal operating system. Android documents Trusty as one TEE implementation. Trusty runs on the same main processor as Android but is isolated through hardware and software. Android supports other TEE implementations too.
The normal Android side is often called the rich execution environment or normal world. The protected TEE side is often called the secure world. The exact implementation depends on the device platform.
Why it matters
If a private key is created and kept inside secure hardware, normal app code does not receive the raw private key bytes. The app asks Android Keystore to perform an operation such as signing. This makes key extraction harder even if normal Android storage or an app process is compromised.
TEE does not mean invincible
A TEE reduces the amount of software that must be trusted, but it is still part of a real hardware and software system. Vulnerabilities, implementation bugs, outdated firmware, or compromised trusted components can still matter. That is one reason remote attestation includes version, boot, and security level information rather than treating the word TEE as enough.
KeyMint and the TEE
KeyMint is the Android security component that implements key creation and protected cryptographic operations. On a TEE backed device, sensitive KeyMint work runs inside the trusted environment. Android Keystore is the application facing system that routes key requests to the correct KeyMint implementation.
Hardware enforced fields matter more
Android attestation separates some rules into softwareEnforced and hardwareEnforced. A rule in hardwareEnforced is backed by the secure hardware boundary. A rule in softwareEnforced depends more heavily on normal Android remaining trustworthy.
When your goal is remote trust, do not stop at checking whether a local API says a key is hardware backed. Verify attestation remotely.
How a TEE works
A TEE is not a second normal Android installation. It is a smaller protected environment that receives only specific security work. Android can ask it to create a key, use a key, confirm security state, or enforce restrictions without exposing the private key to ordinary application memory.
Normal world and secure world
Normal world
Your game, Android framework code, most services, and the Linux kernel run here. This side has much more code and therefore a much larger attack surface.
Secure world
The TEE runs a smaller trusted operating environment and selected trusted services. Access is controlled through narrow interfaces instead of normal process memory.
Hardware boundary
The processor and platform security features keep the secure environment isolated from normal software. The exact implementation depends on the device.
What happens when a protected key signs data
Your app asks Android Keystore to use a key.
Android checks whether the request matches the key rules.
The cryptographic operation is passed to KeyMint in secure hardware when the key is hardware protected.
The secure environment performs the operation without returning the private key material.
Your app receives only the result such as a signature.
Why this helps
If normal Android is compromised, an attacker may still try to ask the secure hardware to use a key. The important difference is that extracting the private key itself is much harder. Key restrictions can also limit what operations the secure hardware allows.
What TEE attestation means
When Android attestation reports TrustedEnvironment for the relevant security level, the attestation is saying the protected operation is backed by a trusted execution environment rather than ordinary software. Your server should still validate the certificate chain, challenge, boot state, application identity, and every other field required by your policy.
TEE is not the same as root detection
A local root detector looks for signs inside Android. TEE backed attestation gives your server evidence from a more trusted boundary. Local root checks can still add telemetry, but they should not replace remote attestation.
StrongBox
StrongBox is Android terminology for KeyMint running in dedicated secure hardware such as an embedded secure element or integrated secure enclave. Android describes StrongBox as providing stronger isolation and tamper resistance than a normal TEE implementation.
StrongBox hardware has its own processing and secure storage capabilities. Android also requires security properties such as a true random number generator and secure timing support. Because it is more isolated and resource constrained, it can be slower and support fewer algorithms or concurrent operations.
TEE compared with StrongBox
| Area | TEE | StrongBox |
|---|---|---|
| Isolation | Protected execution environment on the main processor | Dedicated secure hardware or secure enclave |
| Android attestation level | TrustedEnvironment | StrongBox |
| Performance | Usually faster and more flexible | Usually slower and more constrained |
| Use case | Strong default for hardware backed keys | Higher assurance when the hardware supports it and the threat model needs it |
Do not assume every Android device or every Quest model exposes StrongBox to your application. Detect support and design a policy that has a clear fallback instead of silently pretending StrongBox was used.
Android exposes StrongBox beginning with Android 9 APIs. Apps can request StrongBox backing when generating supported keys, but the request can fail if the secure hardware is unavailable or does not support the requested key parameters.
StrongBox details
StrongBox is intended for cases where TEE isolation is not enough for the threat model. Android requires StrongBox implementations to use dedicated secure hardware with its own processing, protected storage, secure timing, and strong random number generation.
Why StrongBox is harder to attack
StrongBox uses dedicated secure processing instead of relying only on the main application processor.
Key material and secure state are kept in storage designed to preserve confidentiality, integrity, authenticity, and freshness.
StrongBox hardware includes its own source of unpredictable random values for cryptographic work.
A secure timer helps enforce rules without trusting the normal Android clock.
The Android compatibility requirements include resistance to physical tampering, glitch attacks, and side channel leakage.
StrongBox support is not guaranteed
Android exposes FEATURE_STRONGBOX_KEYSTORE so an app can check whether the device advertises StrongBox support. A key request can still fail if the requested algorithm or key size is not supported by the StrongBox implementation.
What to do if StrongBox is unavailable
Decide your fallback before shipping. A normal consumer login might accept a TEE backed key. A very sensitive operation might refuse to continue without StrongBox. The important part is to make the decision explicit instead of silently claiming StrongBox protection when the key actually fell back to something else.
StrongBox is slower for a reason
StrongBox has fewer resources and supports fewer concurrent operations than the main processor. It is best used for security critical key operations, not for every high frequency game operation.
StrongBox protects supported cryptographic keys and operations. It does not turn a modified APK into a trusted APK and it does not replace server validation.
Android Keystore
Android Keystore lets an application create and use cryptographic keys without handling the raw secret key material directly. Depending on the device, keys can be software backed, TEE backed, or StrongBox backed.
Security level
For newer Android targets, KeyInfo.getSecurityLevel can report whether a key is in software, a trusted environment, or StrongBox. This local value can help your app understand where a key lives, but a remote server should use attestation when it needs to trust that answer.
Key restrictions
Keys can be created with restrictions such as allowed algorithms, allowed purposes, digests, padding modes, and user authentication requirements. Hardware backed implementations can enforce some of these restrictions outside normal Android.
Important distinction
Hardware backed key storage protects a cryptographic key. It does not automatically prove that every line of your APK is untampered. Android key attestation and Meta application attestation answer related but different questions.
A hardware key is usually nonexportable
When secure hardware protection is active, the private key material is not handed to your application as raw bytes. The app receives a key handle and asks Keystore to perform allowed operations.
Key rules cannot simply be changed later
Android binds key authorizations when the key is created or imported. If you need different key purposes, algorithms, authentication rules, or security requirements, create a new key instead of assuming the old key can be upgraded in place.
Key lifecycle
A secure key design is more than generating a key once. You need to know where it was created, what it is allowed to do, how the server learns its public key, how long it stays trusted, and what happens when the app is reinstalled or the security state changes.
Creation
Create the key through Android Keystore with the exact purpose you need. If the key only signs challenges, do not give it unrelated permissions. If you need StrongBox, request it and confirm through attestation that the resulting security level matches the policy.
Attestation
Use a fresh server challenge when the key is first registered. Send the certificate chain to the backend and validate it before associating the public key with an account or device record.
Registration
After the chain passes, store the public key or a stable fingerprint of it on the server together with the account, creation time, security level, and the policy result. Do not store the private key because the whole point is that it remains on the device.
Proof of possession
Later, your backend can send a fresh challenge and require the device to sign it with the registered private key. The server verifies the signature with the stored public key. This proves the requester currently has access to that private key without exposing it.
Rotation
Plan how to replace a key after reinstall, reset, corruption, or a policy change. A new key should go through attestation again instead of inheriting trust from an old key automatically.
Revocation
Your backend should be able to disable a registered key if an account is recovered, suspicious activity is confirmed, or you intentionally invalidate old registrations.
Attest once to register a hardware protected key, then use fresh signed server challenges for normal proof of possession. Reattest when your policy says the original device evidence is too old.
Android key attestation
Android key attestation lets a remote verifier examine an X.509 certificate chain associated with a key created in Android Keystore. The attestation extension describes the key and selected device security properties at the time the key was created.
Basic flow
Your server creates a fresh challenge.
The app generates an Android Keystore key with that challenge as the attestation challenge.
The app retrieves the certificate chain for the key.
The app sends the certificate chain to your server.
Your server verifies the chain, root, revocation status, attestation challenge, security level, application identity, boot state, and the fields required by your policy.
Why the challenge matters
The challenge binds the attestation to a request your server created. If you accept an old certificate chain with an unrelated or reused challenge, an attacker may replay evidence that was valid earlier.
Server side verification is required
Android explicitly recommends sending the attestation certificate chain to a separate trusted server. Verifying the chain only on the same potentially compromised device defeats the point of remote verification.
What the attestation is really binding together
A successful result can bind a public key, a server challenge, security level, key authorizations, application identity information, and boot state into signed evidence. Your server should only trust the pieces that are actually present and validated in the attestation record.
Android attestation verification
The safest way to verify Android key attestation is to treat the certificate chain and the attestation record as separate jobs. First prove the chain is trusted. Then interpret the attestation fields. A parser that can read the fields is not automatically a secure verifier.
Recommended verification order
Receive the complete X.509 certificate chain from the device.
Build and validate the certificate path to a currently trusted Android attestation root.
Verify every certificate signature in the path.
Check every applicable certificate against the official Android attestation revocation status list.
Locate the first trusted key attestation extension according to Android guidance.
Decode the attestation record and compare the challenge with the server challenge.
Check attestation security level and KeyMint security level.
Check root of trust, lock state, Verified Boot state, application identity, patch information, and key authorizations required by your policy.
Consume the challenge so the same attestation cannot be accepted again for another registration.
Do not assume the leaf certificate contains the trusted extension
Android warns that an attacker can extend a certificate chain and add an untrusted attestation extension. Your verifier should follow the documented chain rules and trust only the first valid occurrence in the trusted part of the chain.
Revocation matters
Google maintains an official attestation certificate status list. Certificates can be revoked or suspended after key leakage, CA problems, software flaws, or other security events. Cache the list according to its HTTP cache instructions instead of downloading it for every request.
Root changes matter
Do not pin one old root forever. Android currently documents multiple valid attestation roots, including the newer root that started signing chains on February 1, 2026.
Remote Key Provisioning
Modern Android also uses Remote Key Provisioning for attestation keys. Android states that devices launching with Android 16 support only Remote Key Provisioning for this purpose. This changes how attestation keys are provisioned and allows more targeted revocation than older factory provisioned keys.
Use a tested verifier
Android currently recommends its attestation verification Kotlin library because certificate path and attestation edge cases are easy to miss in custom validation code. If you write your own verifier, test it against malformed chains and unusual valid chains instead of testing only the happy path.
Certificate chain
The attestation result is a certificate chain. The attested key certificate appears near the start of the chain and a trusted root appears at the end. Each certificate must be validated as part of the chain.
Checks that matter
Verify that each certificate is correctly signed by the next trusted certificate in the chain.
Use the official Android attestation roots required by your policy. Google added a new attestation root that began signing chains on February 1, 2026, so do not hardcode only an older root.
Check Android attestation certificate revocation information. A valid signature is not enough if a key has been revoked.
The Android key attestation extension uses object identifier 1.3.6.1.4.1.11129.2.1.17.
Android warns not to blindly assume the attestation extension is always in the leaf certificate. Find the first occurrence according to the documented chain rules and do not trust attacker added extensions later in a manipulated chain.
A certificate that parses successfully is not the same as a certificate that is trusted. Parsing, chain validation, root trust, revocation checks, and policy checks are separate steps.
Important Android attestation fields
The exact schema depends on the attestation and KeyMint version. These are some of the fields most useful for understanding device and key state.
| Field | What it tells you |
|---|---|
| attestationSecurityLevel | Where the attestation operation is protected. Values include Software, TrustedEnvironment, and StrongBox. |
| keyMintSecurityLevel | The security level of the KeyMint implementation protecting key operations. |
| attestationChallenge | The challenge supplied when the key was attested. Your server should compare it to the challenge it issued. |
| rootOfTrust | Contains Verified Boot information including boot key, lock state, boot state, and on newer versions the verified boot hash. |
| osVersion | The Android OS version value reported through KeyMint. |
| osPatchLevel | The system security patch level. |
| vendorPatchLevel | The vendor image security patch level on supported attestation versions. |
| bootPatchLevel | The boot or kernel image security patch level on supported attestation versions. |
| attestationApplicationId | The Android platform view of the applications allowed to use the attested key. It can contain package and signing certificate information. |
| softwareEnforced | Authorization values enforced by the Android platform rather than directly by secure hardware. |
| hardwareEnforced | Authorization values enforced by the TEE or StrongBox implementation. |
| purpose | The operations the attested key is authorized to perform, such as signing or decryption. |
| origin | Describes how the key entered KeyMint, such as secure generation or import. |
Hardware enforced and software enforced
The attestation record separates authorization information into softwareEnforced and hardwareEnforced lists. A field appearing in hardwareEnforced has a stronger meaning than the same idea being enforced only by normal Android software.
Verified Boot
Android Verified Boot creates a chain of trust from a hardware protected root through the bootloader and verified partitions. Attestation can expose information about that boot state through rootOfTrust.
| State | Meaning |
|---|---|
| Verified | A trusted chain extends from the hardware root of trust through the verified boot process. |
| SelfSigned | Boot verification used a user configured root instead of the manufacturer root. |
| Unverified | The bootloader is unlocked and the normal verified chain of trust cannot be established. |
| Failed | Boot verification failed and the rest of the root of trust information should not be treated as trustworthy. |
deviceLocked
A locked bootloader is an important part of the trust decision because an unlocked bootloader allows system software to be replaced. Do not use boot state alone as your complete security policy. Combine it with the certificate chain, security level, challenge, app identity, patch state, and your own server session rules.
verifiedBootKey
This value is a digest connected to the public key used for the Verified Boot chain of trust. Its meaning changes with the boot state, so interpret it together with deviceLocked and verifiedBootState.
verifiedBootHash
On attestation versions that include it, this represents a digest of data protected by Verified Boot. Android documents the VBMeta digest here for devices using the Android Verified Boot reference implementation.
Nonces and replay protection
A nonce is a value intended to be used once. In an attestation flow, your server creates a fresh unpredictable value and later requires the returned attestation evidence to contain that exact value.
Good nonce behavior
Use a cryptographically secure random generator. Do not let the client choose the only value used for replay protection.
Associate it with the expected user, session, request type, creation time, and expiry where appropriate.
After verification, require the attested nonce to match the server record exactly.
Mark it used in an atomic server operation before granting the protected action.
A challenge for a login or sensitive operation normally should not remain usable for a long period just because the signed token itself has a longer maximum validity window.
Binding a nonce to a request
For a sensitive action, a strong design can bind the challenge to information such as a server session identifier, transaction identifier, or a hash of important request data. The server recomputes the expected value when the protected request arrives and checks that the verified attestation carries the same nonce.
Meta Quest Attestation API
Meta Quest Application Attestation is a platform service for evaluating whether a request comes from a genuine app binary and a genuine Meta Quest device. Meta documents support for Quest 2, Quest Pro, Quest 3, and Quest 3S. The API is for Android platform apps and requires the client to communicate with Meta attestation services.
Meta flow
Your application server creates a unique challenge_nonce.
The server sends the challenge to the Quest app.
The app calls DeviceApplicationIntegrity.GetIntegrityToken with the challenge.
Meta collects integrity signals and returns a signed attestation token to the app.
The app sends that token to your server.
Your server sends the token to Meta token verification.
Meta verifies the token and returns claims to your server.
Your server validates the claims and applies your access policy.
Current Meta requirements and limits
| Challenge format | Non wrapping Base64URL string created from random bytes by the application server |
|---|---|
| Challenge length | 22 to 172 characters |
| SDK requirement in Meta Unity documentation | At least v55 of the Meta XR SDK and Meta Horizon OS for the documented integration |
| Default token lifetime | 24 hours |
| Device request rate limit | 100 requests per hour and 200 requests per day per device |
Token lifetime and challenge lifetime are different things. Even if a token can be valid for hours, a one use login challenge can still be accepted only once and expire after a short server defined window.
Meta attestation details
Meta application attestation is most useful when you treat the returned token as evidence that still needs a policy decision. Meta verifies the token signature for you and returns claims. Your backend then decides which claim values are acceptable for the action being requested.
What Meta is trying to answer
The app integrity state tells you whether Meta recognizes the installation as coming from the Horizon Store.
The package certificate digest lets your backend compare the signed claim with the certificate digest values that belong to your real release.
The package ID claim should match the package ID configured on your server.
The device integrity state reports Advanced, Basic, or NotTrusted based on Meta security signals.
The nonce, timestamp, and expiration give your server enough information to reject replayed or stale evidence.
Advanced
Meta describes Advanced as a device with a locked bootloader, a valid Android system image, and no signs of system compromise. This is the strongest normal device integrity result documented for the API.
Basic
Basic still means the bootloader is locked and the Android image is valid, but another integrity check failed. Meta specifically notes that this may indicate compromise after boot, including rooting. Whether Basic is acceptable depends on your threat model.
NotTrusted
NotTrusted means Meta saw an unlocked bootloader or an invalid Android system image. For an endpoint that is supposed to require trusted Quest hardware, this should normally fail the integrity policy.
StoreRecognized
StoreRecognized means Meta recognizes the app as legitimately installed through the Meta Quest Store. A server protecting a public store build can normally require this value together with the expected package ID and signing digest.
NotRecognized and NotEvaluated
NotRecognized means the installation is not recognized as legitimate. NotEvaluated means Meta could not identify the app for that evaluation. Do not silently convert either value into StoreRecognized. Decide whether development builds need a separate test policy.
Timestamp should be recent
Meta recommends checking that the timestamp is recent, within a few minutes or less for a normal validation flow. This is separate from the maximum token expiration time. A token that has not expired can still be too old for a login you want to treat as fresh.
Token caching
Meta allows applications to cache attestation tokens within their validity period and recommends caching to avoid rate limits. Your backend should still decide whether a cached token is fresh enough for the current action. Login and account recovery can use a stricter freshness rule than a low risk request.
Rate limits
Meta currently documents a limit of 100 attestation requests per hour and 200 per day for each device. Do not request a new token for every network packet or every gameplay event.
Error handling
A token request can fail because of network problems or platform issues, but malicious software can also block the call. Meta recommends retries with increasing delay. If the app still cannot obtain a token after the retry policy, protected backend access should not be granted as though attestation passed.
Meta verification success proves the token is authentic. Your own backend still has to check the claims. Signature verification alone is not the final security decision.
Meta token claims
After Meta verifies an attestation token, your server receives attestation claims. Your policy should validate the fields you depend on instead of treating a successful verification response as the entire authorization decision.
Request details
| timestamp | UTC creation time of the token in seconds. |
|---|---|
| exp | Expiration time. Meta documents a default of 24 hours after creation. |
| nonce | The challenge_nonce supplied to GetIntegrityToken. It must match the server challenge you expected. |
App state
| app_integrity_state | StoreRecognized means the app installation is recognized as legitimate by the Meta Quest Store. NotRecognized indicates an illegitimate installation. NotEvaluated means the application could not be identified for that evaluation. |
|---|---|
| package_cert_sha256_digest | SHA256 digest values for the app signing certificate signatures. Your server can compare them with the signing certificate values you expect. |
| package_id | The package name reported in the attestation claims. |
| version | The package version reported in the attestation claims. |
Device state
| Advanced | Meta reports a locked bootloader, valid Android system image, and no signs of system compromise. |
|---|---|
| Basic | Meta reports a locked bootloader and valid Android system image, but one or more other integrity checks failed and may indicate compromise after boot. |
| NotTrusted | Meta reports an unlocked bootloader or an invalid Android OS image. |
| unique_id | App scoped device identifier used by the attestation service. Meta states that it changes every 30 days for privacy. |
| security_update_pending_days | Number of days since the oldest public security patch missing on the device. Meta advises caution because updates may reach users at different times. |
How to read the claims safely
Do not compare the entire decoded object with one fixed JSON shape. Read the fields your policy requires, validate their types and allowed values, and safely ignore new fields you do not use. This keeps a future Meta claim addition from breaking your authentication flow.
Package certificate digests
The certificate digest field can contain more than one value. Keep a server side set of signing certificate digests you currently trust and compare the verified claim against that set. This also gives you a controlled way to handle legitimate signing changes.
Version policy
The version claim can help enforce a minimum supported app version, but do not use it as the only integrity check. A valid version string does not replace StoreRecognized, signing digest validation, or device integrity.
Write your claim parser to tolerate new unknown fields. Reject bad values in fields you require, but do not crash just because Meta adds another claim later.
Meta device bans
Meta provides a device ban feature that works with the Attestation API. Your backend identifies the device through the app scoped unique ID from a verified attestation result and sends the ban request to Meta from the server.
How the ban signal is used
Your backend verifies a normal Meta attestation token.
The backend reads the current unique ID from the verified device state.
Your backend creates a device ban through the Meta server interface when your moderation policy allows it.
Later attestation results can include a device ban section showing that the device is banned.
Your own backend enforces the restriction when it sees the ban status.
The unique ID rotates
Meta states that the attestation unique ID is scoped to your application and changes every 30 days for privacy. Do not build a permanent hardware identity system around the assumption that this value never changes.
Keep the ban ID when you create a ban
Meta returns a ban ID for a created ban. Meta documents that the ban ID can continue to identify that ban even after the current unique ID has rotated, until the ban expires or is reversed.
Access requirement
The device ban feature requires Attestation API integration and approval through Meta Data Use Checkup. The normal attestation API itself and the optional device ban feature should be treated as separate parts of your design.
Use bans carefully
Attestation can help identify severe repeated abuse, but a ban decision is still your responsibility. Keep moderation evidence and account controls separate from the raw integrity verdict so one temporary device problem does not automatically become a permanent punishment.
Meta user verification
Meta User Verification is separate from application attestation. User Verification helps your server verify that a Meta account identifier supplied by the client is valid for the user providing the proof.
The client obtains a user proof nonce through the Platform SDK. Your trusted server sends that nonce and the Meta user identifier to Meta using the user_nonce_validate server to server endpoint. A valid response confirms the user proof. Meta also documents retrieving a verified org scoped ID after the user proof is validated.
What it does not replace
Meta states that User Verification does not replace the Entitlement Check. User identity, ownership entitlement, app integrity, and device integrity are different signals and should not be treated as interchangeable.
Bind the verified user to your own session
After the backend verifies the Meta user proof, connect that verified identity to the same server login attempt that passed your application and device integrity checks. Do not verify the user in one request and then trust a different client supplied user ID later.
Useful separation
| Question | Relevant mechanism |
|---|---|
| Is this the expected Meta user | User Verification |
| Does the user own or have access to the app | Entitlement Check |
| Is this recognized app binary and device state acceptable | Meta Quest Attestation API |
| Is this Android Keystore key hardware backed with expected properties | Android key attestation |
Combining security signals
The strongest design does not ask one check to do every job. Use each signal for the question it is good at, then let the backend combine the results into one session decision.
Example Quest login
The backend starts a login attempt and creates a fresh one use nonce.
The app obtains Meta application attestation using that nonce.
The backend verifies the Meta token and checks freshness, app identity, signing digest, and device integrity.
The backend verifies the Meta user proof for the account identifier.
The backend performs the entitlement check if ownership is required.
If you use an Android hardware key, the backend can also verify its registration or require proof of possession.
The backend binds the verified account and approved security state to its own short lived session.
Why this is better than one giant check
If account proof fails, you know the identity layer failed. If the package digest is wrong, you know the app identity layer failed. If device integrity is NotTrusted, you know the platform integrity layer failed. Keeping the checks separate makes logs easier to understand and policy changes safer.
Use hardware keys for continuity, not as a replacement for attestation
A registered TEE or StrongBox key can be useful for proving that a later request comes from the same protected key. It does not automatically tell you that the current app still has the same Meta integrity state as when the key was registered. Reattestation is still useful when you need a fresh platform verdict.
Use server sessions to avoid checking everything constantly
Once the login passes, create your own session with a clear expiration time and the security facts you care about. Normal API requests can validate that session. Sensitive actions can require a fresh integrity check when needed.
Store the result, not every secret
Store safe facts such as verification time, account identifier, allowed integrity level, key fingerprint, package version, and policy result. Avoid storing raw reusable tokens longer than necessary.
Server validation
The server should be the security boundary. It should not accept a client result such as attestationValid equals true. It should validate the real evidence or ask the platform provider to validate it.
Recommended order for Meta application attestation
Look up the server side challenge record.
Reject missing, expired, already used, or wrong session challenges.
Send the attestation token to Meta verification from the server.
Require Meta verification success.
Decode the returned claims.
Compare nonce with the server challenge.
Check timestamp and expiration against your own freshness policy.
Check package_id, signing certificate digest, app integrity state, and acceptable device integrity state.
Atomically consume the challenge.
Issue your own short lived authenticated server session.
Keep secrets off the client
Meta server to server credentials belong only on your backend. Do not put an app secret or server access token in Unity code, JavaScript shipped to a browser, an APK asset, or a public repository.
Fail closed for protected actions
For high value actions, a missing attestation result should not silently become success. Meta specifically warns that compromised clients may block or strip the attestation call. Use controlled retries for temporary network problems, then apply the same cautious policy you would apply to an untrusted integrity result.
Example security policy
A policy should match your app instead of copying the strictest possible setting. A multiplayer game login, a purchase, an account recovery action, and a low risk public endpoint do not need identical rules.
Example login policy
Valid server challenge, successful Meta token verification, exact nonce match, expected package ID, expected signing certificate digest, recognized app state, acceptable device integrity state, recent token timestamp, and a challenge that has not been used before.
A server session token with a shorter lifetime than the attestation token. Bind the session to the verified account or internal player identity.
Run attestation again when the session expires, after a meaningful risk event, or before a sensitive operation that needs fresher integrity evidence.
Patch policy
Do not automatically ban every device that is one day behind a new security update. Meta warns that update rollout speed varies. A reasonable policy can log patch age, apply a warning window, and become stricter only when the risk of a known old build justifies it.
Basic versus Advanced
If your threat model needs the strongest Meta device signal, require Advanced. If your app can safely support Basic, document exactly what access Basic receives. Do not accidentally treat NotTrusted as a temporary warning if the protected endpoint is supposed to require an intact device.
What attestation cannot prove
Attestation is strong evidence, not magic. A good design understands its limits.
Attackers can still inspect application files, native libraries, network behavior, and public protocol formats.
A genuine app on a genuine device does not automatically mean the user is allowed to perform every action.
Server authoritative gameplay, rate limits, inventory validation, purchase validation, and abuse prevention logic still matter.
A signed token can still be inappropriate for a new transaction if the challenge was not bound to that transaction or if your freshness window is too large.
Integrity signals describe software, hardware, or account proof. They do not replace moderation, fraud detection, or behavioral controls.
Common mistakes
Validating only on the client
A modified client can patch the result. The server needs to make the final decision.
Trusting client supplied hashes
A client can send the hash value you expect. Prefer signed attestation claims or server side platform verification.
Reusing nonces
A reusable challenge turns fresh attestation into replayable evidence.
Checking only the signature
A valid signed token still needs claim checks. Validate nonce, time, app identity, signing certificate, and integrity verdicts.
Hardcoding one Android attestation root forever
Trust roots can rotate. Android added another root for chains beginning February 1, 2026. Follow the official root list rather than copying an old single certificate from a tutorial.
Confusing identity with integrity
Knowing the Meta account does not prove the APK is intact. Knowing the APK is recognized does not prove the current account owns the app.
Using a permanent device identifier assumption
Meta documents that the attestation unique_id is app scoped and rotates every 30 days.
Rejecting every temporary attestation error immediately
Network and infrastructure failures happen. Retry with backoff, but do not turn repeated failure into automatic trust.
Testing
Test the negative path as carefully as the success path. A security system that works only when every service is healthy is incomplete.
| Test | Expected server behavior |
|---|---|
| Correct token and correct nonce | Continue only if all required claims also pass. |
| Correct token with wrong server nonce | Reject. |
| Same challenge submitted twice | First valid use may succeed. Later use is rejected. |
| Wrong package ID | Reject. |
| Wrong signing certificate digest | Reject. |
| Expired token | Reject and request fresh evidence. |
| Old timestamp inside your freshness window policy | Apply the policy you documented. Do not rely only on maximum token lifetime. |
| Meta service unavailable | Retry with backoff. Protected operations remain unavailable if verification cannot be established. |
| Unexpected new claim | Ignore unknown fields safely while continuing to enforce known required fields. |
Validation tips
Good attestation is mostly about what your server refuses to trust. Keep the rules small, check the important values every time they matter, and never let the client decide whether it passed.
Server side tips
Do not let the client choose its own challenge. Generate a random nonce on the backend, save it with a short expiration time, and send that nonce to the client.
After a valid attestation uses a nonce, mark it used in the same database operation that accepts the request. Two requests should never be able to spend the same nonce at the same time.
Save which user, login attempt, session, or protected action the nonce belongs to. A valid token for one login should not be accepted for another login.
Check the returned package ID and signing certificate digest against values stored on your server. Do not accept the package name or expected hash from the client.
A token can be correctly signed and still contain a result your app should reject. Check the app integrity state and device integrity state instead of stopping after token verification succeeds.
Meta tokens have their own expiration, but your backend can require a much newer token for login or a sensitive action. Check the timestamp, expiration, and nonce together.
You usually do not need to run full attestation on every normal game request. After a successful login check, issue your own short lived authenticated session and validate that session on later requests.
Run a fresh check for a new login, a new device session, account recovery, important purchases, or other actions where an old integrity result is not enough.
Do not trust these from the client
| Client value | What the server should do instead |
|---|---|
| attestationValid | Verify the real token with Meta and check the returned claims. |
| package ID | Compare the verified claim with the package ID stored in server configuration. |
| signing hash | Compare the verified signing digest with a server side allowlist. |
| device integrity result | Use the value returned by the trusted attestation result. |
| user ID by itself | Verify the user through the platform flow and bind it to the server session. |
| request timestamp | Use server time for expiration and replay decisions. |
Android hardware attestation tips
Do not validate a hardware attestation chain only on the same Android device that produced it. A compromised device could also interfere with the local validation code.
Check the trusted root, every certificate signature in the chain, certificate validity, and revocation status. Successfully parsing the certificates is not enough.
The attestation challenge should match the fresh challenge your server created. This proves the attestation was created for the request your server is currently checking.
Read the attestation security level and KeyMint security level. If your policy requires hardware protection, do not silently treat a software backed result as equivalent to TEE or StrongBox.
Verified Boot information can help show whether the device booted software that matches its trusted boot configuration. Decide which states your app accepts before you ship.
Do not copy one old Android attestation root into your backend and forget it. Google can rotate or add attestation roots, so keep your trust list based on the current Android documentation.
Small things that prevent big mistakes
Meta app secrets, server access tokens, database credentials, and signing secrets should never be shipped inside the APK.
The client usually only needs to know that authentication failed. Keep the exact failed check in private server logs so you do not give attackers a map of your validation rules.
Redact attestation tokens, session tickets, access tokens, app secrets, and other reusable credentials. Log request IDs and safe identity fields instead.
Do not allow one client or account to create unlimited challenges or verification requests. Expire unused challenges and limit how many can be active at once.
A timeout is not the same thing as a bad integrity verdict. You can retry temporary service errors while still refusing protected access until validation actually succeeds.
Try an expired nonce, reused nonce, wrong package, wrong signing digest, expired token, missing token, and two requests using the same nonce. Security code is only useful if the rejection paths work too.
Trust verified evidence from the platform, values your own server created, and data already tied to an authenticated server session. Treat everything else from the client as input that still needs validation.
Official references
These are the primary sources used for the technical details in this site. Check them again before shipping security logic because platform behavior, SDK requirements, trust roots, limits, and recommendations can change.
Content reviewed against official documentation on August 20, 2026.