Skip to main content

Play Integrity

The Play Integrity API is used to verify that your Android app:

  1. Has not been altered compared to what was submitted to the Play Store.
  2. Has a user account that is licensed (the user installed or paid for your app on Google Play).
  3. Is installed on a genuine Android device powered by Google Play services.
  4. Is free from malware as determined by Google Play Protect.

Using the Play Integrity API is a powerful way to ensure that the API services you expose are only accessed by your mobile application. This can prevent API access by bots, scripts etc which helps protect your users and your services.

This tutorial shows you how to integrate the Play Integrity API in your Capacitor application by following these steps:

  1. Link your cloud project.
  2. Install the Play Integrity Plugin.
  3. Get an Integrity Token.
  4. Backend Verification.

In the Google Play Console you need to go to App Integrity and link your Google Cloud Project by clicking the Link Cloud project button. Note the Google Cloud Project Number for use in your app.

Install the Play Integrity Plugin

We need to install a plugin called @capacitor-community/play-integrity:


_10
npm install @capacitor-community/play-integrity
_10
npx cap sync

The plugin is used to get an Integrity Token.

The Integrity Token

The Integrity Token is provided by the Android device and represents an encrypted reference that your backend can use to check the integrity of the device. We'll send this token to our backend later in the tutorial.

On the startup of our application, we call to requestIntegrityToken:


_10
import { PlayIntegrity } from '@capacitor-community/play-integrity';
_10
...
_10
try {
_10
const result = await PlayIntegrity.requestIntegrityToken({
_10
nonce: nonce,
_10
googleCloudProjectNumber: 0
_10
});
_10
} catch (err) {
_10
// Recommendation: Report to the backend and exit the application
_10
}

The Integrity Token is returned in result.token.

More information about the requestIntegrityToken method can be found in the Android documentation.

There are two parameters we needed to supply:

  • nonce - This is a unique value that you will strategically generate (see Docs for generating a nonce)
  • googleCloudProjectNumber - This number can be set to 0 or can be set to the Project Number you find in Firebase Console in Project Settings > General. If set to 0 it will default to the project associated with the application.

Where to use an Integrity Token

Integrity Tokens are combined with API calls to your backend. It is usually associated with an API call that is "High Value" such as a login (or first call after), obtaining or modifying personal information, performing a transaction or even as an initial check on the first startup of the app.

You may include the integrity token as an additional header in the call to your backend.

When not to use an Integrity Token

Google will validate up to 10,000 Integrity Tokens per day. This limit can be expanded, or you can decrypt tokens on your backend without calling Google's APIs. Given these limitations, and the fact that obtaining a token can take a few seconds, you should be selective about when to obtain and verify integrity tokens.

This means that you should not request and validate on every API call, but instead on the first startup or as part of a login process.

Send to Backend

You will need to send the Integrity Token to your backend. You may add this as an additional header to an existing API call.

The important part is that your App handles an integrity check failure and takes appropriate action, such as preventing further interaction in the app.

Backend Verification

An Integrity Token is not a guarantee that the device is legitimate. To get a verdict on whether a device is "ok" you must check the token on your backend.

For this tutorial, we are using a Cloudflare Worker as our backend.

These are the steps in creating the backend:

Creating a Service Account

In the Play Console visit App Integrity > Google Cloud project: View project. This visits console.cloud.google.com and allows us to click Create Credentials:

  • The API should be "Google Play Integrity API".
  • Check the radio button Application Data.
  • Click Next.
  • Give the account a name (eg play-integrity).
  • Give the account a description (eg Verify my app Play Integrity).
  • Click the Create and Continue button.
  • Choose a Role (I chose Firebase Admin SDK).
  • Click Done.

Next, we need a key for this service account:

  • Click the service account you created.
  • Click the Keys tab.
  • Click Add Key and choose Create new key.
  • Choose the JSON key type and click Create.
  • This file is a secret and in our Sample Project we upload it to Cloudflare as an environment variable (we call it GOOGLE_CLOUD_CREDENTIALS).

Our Backend Code

We'll follow this guide to use Google's servers to decrypt the Play Integrity token.

First we need an access token:


_10
const accessToken = await getAccessToken({
_10
credentials: env.GOOGLE_CLOUD_CREDENTIALS,
_10
scope: "https://www.googleapis.com/auth/playintegrity",
_10
});

Note that we are using env.GOOGLE_CLOUD_CREDENTIALS which is the contents of the JSON file we obtained earlier.

We can then call to decode the integrity token:


_12
const res = await fetch(
_12
`https://playintegrity.googleapis.com/v1/${env.PACKAGE_NAME}:decodeIntegrityToken`,
_12
{
_12
method: 'POST',
_12
headers: {
_12
'Authorization': `Bearer ${accessToken}`,
_12
'Content-Type': `application/json`
_12
},
_12
body: JSON.stringify({ integrity_token: integrityToken })
_12
}
_12
);
_12
const response = await res.json();

In the above code:

  • integrityToken is a variable passed in from the mobile application.
  • env.PACKAGE_NAME is a variable that is our Android app's package name.
  • response is a JSON object that gives us the verict of whether the mobile application client looks legitimate.

Here's an example response that it may return:


_25
{
_25
"tokenPayloadExternal": {
_25
"requestDetails": {
_25
"requestPackageName": "com.myapp",
_25
"timestampMillis": "1705428550115",
_25
"nonce": "3e8a756e-9555-40ea-bb18-18ebe68fcd90"
_25
},
_25
"appIntegrity": {
_25
"appRecognitionVerdict": "UNRECOGNIZED_VERSION",
_25
"packageName": "com.myapp",
_25
"certificateSha256Digest": [
_25
"lDDakzlkObg9sTcq2Rh8VnTBu7bUVtNPJnWogSiaiLM"
_25
],
_25
"versionCode": "49"
_25
},
_25
"deviceIntegrity": {
_25
"deviceRecognitionVerdict": [
_25
"MEETS_DEVICE_INTEGRITY"
_25
]
_25
},
_25
"accountDetails": {
_25
"appLicensingVerdict": "LICENSED"
_25
}
_25
}
_25
}

Based on this response's appLicensingVerdict, appRecognitionVerdict, and deviceRecognitionVerdict we make a judgment call as to whether we think this device should pass.

For example, this code returns a status code 200 back to the mobile app if it looks ok, otherwise a status code of 401.


_10
if (response.tokenPayloadExternal.appIntegrity.appRecognitionVerdict == 'PLAY_RECOGNIZED' &&
_10
response.tokenPayloadExternal.deviceIntegrity.deviceRecognitionVerdict.includes('MEETS_DEVICE_INTEGRITY') &&
_10
response.tokenPayloadExternal.accountDetails.appLicensingVerdict == 'LICENSED'
_10
) {
_10
return new Response('Your device looks legit!');
_10
} else {
_10
console.error('Failed Play Integrity', response);
_10
return new Response("Failed", { status: 401 });
_10
}

This covers the most basic use case. If you have gotten this far, Congratulations! You have a rudimentary check on the integrity of the device calling your API.

Next Steps

There is more to learn and your best resource is the official docs:

Summary

If you are reading this tutorial you understand that your API may be accessed by someone other than legitimate users. Using the Play Integrity API will help you enforce limiting that access.

The Play Integrity API is one layer in the overall security of your App. Other topics you may like to explore include Root Kit Detection, Code Obsfucation, SSL Pinning, Security provider updates and storage security.

Common Questions

  • What about iOS? - For the iOS platform the Device Check provides an equivalent.

  • What about the standard request? - The plugin in this tutorial uses a classic request. A standard request is not supported yet but may be in the future as standard requests can be faster after the initial call.

  • What about QA/Dev testing? - Your application may want to have different rules for checking the verdict when it is in a staging/dev environment. For example, you may allow UNRECOGNIZED_VERSION for the appRecognitionVerdict as the app may be installed from Android Studio.

  • What about SafetyNet Attestation? - The SafetyNet Attestation API is the predecessor to the Play Integrity API. If you are using it you should migrate to the Play Integrity API.