Play Integrity
The Play Integrity API is used to verify that your Android app:
- Has not been altered compared to what was submitted to the Play Store.
- Has a user account that is licensed (the user installed or paid for your app on Google Play).
- Is installed on a genuine Android device powered by Google Play services.
- 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:
- Link your cloud project.
- Install the Play Integrity Plugin.
- Get an Integrity Token.
- Backend Verification.
Link Cloud Project
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
:
_10npm install @capacitor-community/play-integrity_10npx 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
:
_10import { PlayIntegrity } from '@capacitor-community/play-integrity';_10..._10try {_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 theProject Number
you find in Firebase Console inProject Settings
>General
. If set to0
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:
- Create a Service Account for our App's Google Cloud Project.
- Create a backend API that accepts the Integrity Token. See the Sample Project.
- Call the Play Integrity API with the Integrity Token to get a Verdict Response.
- Allow or Deny the App based on the verdict.
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 chooseCreate new key
. - Choose the
JSON
key type and clickCreate
. - 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:
_12const 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);_12const 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
.
_10if (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:
- Enable features like verifying Play Protect, or device activity.
- Handle and understand possible error codes.
- Avoid common security and performance mistakes.
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 theappRecognitionVerdict
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.