This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

AppIntegrity

A library that provides access to Google's Play Integrity API on Android and Apple's App Attest service on iOS.

Android
iOS
Included in Expo Go

@expo/app-integrity provides APIs to help ensure your backend resources are accessed only by legitimate installations of your app running on genuine devices. It uses Google's Play Integrity APIs on Android and Apple's App Attest service on iOS to verify app authenticity, helping prevent unauthorized clients, modified apps, or automated scripts from making requests to your server.

Generally, @expo/app-integrity helps your server tell the difference between:

  • Your real app running on a real device
  • Anything else (modified apps, scripts, emulators)

It does this by using the platform-recommended app attestation services.

Installation

Terminal
- npx expo install @expo/app-integrity
- yarn expo install @expo/app-integrity
- pnpm expo install @expo/app-integrity
- bun expo install @expo/app-integrity

If you are installing this in an existing React Native app, make sure to install expo in your project.

Usage on Android

@expo/app-integrity uses Play Integrity's Standard request flow for integrity checks.

Configuration

Refer to the Play Integrity setup guide for instructions to enable integrity APIs in your app.

Prepare the integrity token provider (one time)

You need to prepare the integrity token provider before you make integrity check requests. You can do this when your app launches or in the background before the integrity check is needed.

import * as AppIntegrity from '@expo/app-integrity'; const cloudProjectNumber = 'your-cloud-project-number'; await AppIntegrity.prepareIntegrityTokenProviderAsync(cloudProjectNumber);

Request an integrity token (on demand)

Whenever your app makes a server request that you want to check is genuine, you request an integrity token and send it to your app's backend server for decryption and verification. Then, your backend server can determine how to act.

const requestHash = '2cp24z...'; const result = await AppIntegrity.requestIntegrityCheckAsync(requestHash);

Before calling requestIntegrityCheckAsync, ensure that prepareIntegrityTokenProviderAsync was called successfully.

In this example, requestHash is a hash unique to the specific user action being verified. You can call requestIntegrityCheckAsync multiple times with different hashes for different user actions.

On success, send the result to your server for verification.

Decrypt and verify the integrity verdict

Refer Play Integrity's guide to verify the integrity token in your server.

Additional resources

  • Google Pay Integrity documentation: Refer to Google's official guide to understand the APIs and verification flow that power @expo/app-integrity.

  • Play Integrity Standard request flow: This page describes making standard API requests for integrity verdicts, which are supported on Android 5.0 (API level 21) or higher. You can make a standard API request for an integrity verdict whenever your app is making a server call to check whether the interaction is genuine.

  • About Integrity verdicts: The integrity verdict communicates information about the validity of devices, apps, and accounts. Your app's server can use the resulting payload in a decrypted, verified verdict to determine how best to proceed with a particular action or request in your app.

  • Handling error codes: If your app makes a Play Integrity API request and the call fails, your app receives an error code. These errors can happen for various reasons, such as environmental issues like a weak network connection, problems with your API integration, or malicious activity and active attacks.

Usage on iOS

Configuration

In Xcode, go to Signing & Capabilities, click + Capability, add App Attest. Xcode will automatically add the required entitlement to your app.

For verification logic on your server, see Validating apps that connect to your server.

Check if the device supports app attestation

Not all devices can use the App Attest service, so it's important to have your app run a compatibility check before accessing the service. If the user's app doesn't pass the compatibility check, gracefully bypass the service. You can check for availability by reading the isSupported property.

import * as AppIntegrity from '@expo/app-integrity'; if (AppIntegrity.isSupported) { // Perform key generation and attestation. } // Continue with your server API access.

Create a key pair

For each user account on each device running your app, generate a unique, hardware-based, cryptographic key pair by calling the generateKey method.

const keyId = await AppIntegrity.generateKeyAsync();

On success, the method returns a key identifier (keyId) that you use later to access the key. Record the identifier in persistent storage because there's no way to use the key without the identifier and no way to get the identifier later. The device automatically stores the associated private key in the Secure Enclave, from where the App Attest service can use it to create signatures, but from where no process can ever directly read or modify it, ensuring its security.

Don't reuse a key among multiple users on a device because this weakens security protections. In particular, it becomes hard to detect an attack that uses a single compromised device to serve multiple remote users running a compromised version of your app. For more information, see Assessing fraud risk.

Get a challenge from your server

Request a unique, one-time challenge from your server. This challenge will be embedded in the attestation step below, ensuring it can't be reused by an attacker. The challenge should be at least 16 bytes long to provide enough entropy so that guessing it is infeasible.

Certify the key pairs as valid

Pass the keyId alongwith the challenge from your server created in the previous steps in attestKey method as shown below:

const attestationObject = await AppIntegrity.attestKeyAsync(keyId, challenge);

On success, send the received attestationObject and the keyId to your server for verification.

If the method returns ERR_APP_INTEGRITY_SERVER_UNAVAILABLE error, try attestation again later with the same key. For any other error, discard the key identifier and create a new key when you want to try again.

Your server deems the app instance to be valid if it can successfully verify the attestation object. In this case, be sure to persistently store the key identifier — not the attestation object — in your app to sign server requests in the future.

Generate assertions on sensitive requests

After successfully verifying a key's attestation, your server can require the app to assert its legitimacy for any or all future server requests. The app does this by signing the request. In the app, obtain a unique, one-time challenge from the server. You use a challenge here, like for attestation, to avoid replay attacks.

const challenge = 'A string from your server'; const request = { action: 'getGameLevel', levelId: '1234', challenge: challenge, }; const assertion = await AppIntegrity.generateAssertionAsync(keyId, JSON.stringify(request));

On success, pass the assertion object, along with the client data, to the server. If the assertion object fails verification, it's your responsibility to decide how to handle the request.

There's no restriction on the number of assertions that you can make with a key. Nevertheless, you typically reserve assertions for requests made at sensitive moments in your app's life cycle, like when the app downloads premium content.

Start over on reinstallation

The keys that you generate remain valid through regular app updates, but don't survive app reinstallation, device migration, or restoration of a device from a backup. In these cases, you need to start the process from the beginning and generate a new key. Try to limit new key generation to only these events, or to the addition of new users. Keeping the key count low on a device helps when trying to detect certain kinds of fraud.

Additional resources

API

import * as AppIntegrity from '@expo/app-integrity';

No API data file found, sorry!