Make a standard API request

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.

Overview

Sequence diagram that shows the high-level design of the Play Integrity
API

A standard request consists of two parts:

  • Prepare the integrity token provider (one off): You need to call the Integrity API to prepare the integrity token provider well before you need to obtain the integrity verdict. For example, you can do this when your app launches or in the background before the integrity verdict is needed.
  • 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 decide how to act.

Prepare the integrity token provider (one off):

  1. Your app calls the integrity token provider with your Google Cloud project number.
  2. Your app holds the integrity token provider in memory for further attestation check calls.

Request an integrity token (on demand):

  1. For the user action which needs to be protected, your app computes the hash (using any suitable hash algorithm such as SHA256) of the request to be made.
  2. Your app requests an integrity token, passing the request hash.
  3. Your app receives the signed and encrypted integrity token from the Play Integrity API.
  4. Your app passes the integrity token to your app's backend.
  5. Your app's backend sends the token to a Google Play server. The Google Play server decrypts and verifies the verdict, returning the results to your app's backend.
  6. Your app's backend decides how to proceed, based on the signals contained in the token payload.
  7. Your app's backend sends the decision outcomes to your app.

Prepare the integrity token provider (one off)

Before you make a standard request for an integrity verdict from Google Play, you must prepare (or "warm up") the integrity token provider. This allows Google Play to smartly cache partial attestation information on the device in order to decrease the latency on the critical path when you make a request for an integrity verdict. Preparing the token provider again is a way to repeat less resource heavy integrity checks which will make the next integrity verdict that you request more up to date.

You might prepare the integrity token provider:

  • When your app launches (i.e. on cold start up). Preparing the token provider is asynchronous and so will not impact the start up time. This option would work well if you plan to make an integrity verdict request shortly after the app is launched, for example when a user signs in or a player joins a game.
  • When your app is opened (i.e. on warm start up). However, note that each app instance can only prepare the integrity token up to 5 times per minute.
  • At any time in the background when you want to prepare the token in advance of an integrity verdict request.

To prepare the integrity token provider do the following:

  1. Create a StandardIntegrityManager, as shown in the following examples.
  2. Construct a PrepareIntegrityTokenRequest, supplying the Google Cloud project number through the setCloudProjectNumber() method.
  3. Use the manager to call prepareIntegrityToken(), supplying the PrepareIntegrityTokenRequest.

Java

import com.google.android.gms.tasks.Task;

// Create an instance of a manager.
StandardIntegrityManager standardIntegrityManager =
    IntegrityManagerFactory.createStandard(applicationContext);

StandardIntegrityTokenProvider integrityTokenProvider;
long cloudProjectNumber = ...;

// Prepare integrity token. Can be called once in a while to keep internal
// state fresh.
standardIntegrityManager.prepareIntegrityToken(
    PrepareIntegrityTokenRequest.builder()
        .setCloudProjectNumber(cloudProjectNumber)
        .build())
    .addOnSuccessListener(tokenProvider -> {
        integrityTokenProvider = tokenProvider;
    })
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator PrepareIntegrityTokenCoroutine() {
    long cloudProjectNumber = ...;

    // Create an instance of a standard integrity manager.
    var standardIntegrityManager = new StandardIntegrityManager();

    // Request the token provider.
    var integrityTokenProviderOperation =
      standardIntegrityManager.PrepareIntegrityToken(
        new PrepareIntegrityTokenRequest(cloudProjectNumber));

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenProviderOperation;

    // Check the resulting error code.
    if (integrityTokenProviderOperation.Error != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenProviderOperation.Error);
        yield break;
    }

    // Get the response.
    var integrityTokenProvider = integrityTokenProviderOperation.GetResult();
}

Native

/// Initialize StandardIntegrityManager
StandardIntegrityManager_init(/* app's java vm */, /* an android context */);
/// Create a PrepareIntegrityTokenRequest opaque object.
int64_t cloudProjectNumber = ...;
PrepareIntegrityTokenRequest* tokenProviderRequest;
PrepareIntegrityTokenRequest_create(&tokenProviderRequest);
PrepareIntegrityTokenRequest_setCloudProjectNumber(tokenProviderRequest, cloudProjectNumber);

/// Prepare a StandardIntegrityTokenProvider opaque type pointer and call
/// StandardIntegrityManager_prepareIntegrityToken().
StandardIntegrityTokenProvider* tokenProvider;
StandardIntegrityErrorCode error_code =
        StandardIntegrityManager_prepareIntegrityToken(tokenProviderRequest, &tokenProvider);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_provider_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_getStatus(tokenProvider, &token_provider_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_provider_status == INTEGRITY_RESPONSE_COMPLETED)
{
    /// continue to request token from the token provider
}
/// ...
/// Remember to free up resources.
PrepareIntegrityTokenRequest_destroy(tokenProviderRequest);

Protect requests against tampering (recommended)

When you're checking a user action in your app with the Play Integrity API, you can leverage the requestHash field to mitigate against tampering attacks. For example, a game may want to report the player's score to the game's backend server, and your server wants to ensure this score has not been tampered with by a proxy server. The Play Integrity API returns the value you set in the requestHash field, inside the signed integrity response. Without the requestHash, the integrity token will be bound only to the device, but not to the specific request, which opens up the possibility of attack. The following instructions describe how to make use of the requestHash field effectively:

When you request an integrity verdict:

  • Compute a digest of all relevant request parameters (e.g. SHA256 of a stable request serialization) from the user action or server request that is happening. The value set in the requestHash field has a maximum length of 500 bytes. Include any app request data in the requestHash that is crucial or relevant to the action that you are checking or protecting. The requestHash field is included in the integrity token verbatim, so long values may increase the request size.
  • Provide the digest as the requestHash field to the Play Integrity API, and obtain the integrity token.

When you receive an integrity verdict:

  • Decode the integrity token, and extract the requestHash field.
  • Compute a digest of the request in the same manner as in the app (e.g. SHA256 of a stable request serialization).
  • Compare the app-side and server-side digests. If they do not match, the request is not trustworthy.

Request an integrity verdict (on demand)

After you have prepared the integrity token provider, you can start requesting integrity verdicts from Google Play. To do so, complete the following steps:

  1. Obtain a StandardIntegrityTokenProvider, as shown above.
  2. Construct an StandardIntegrityTokenRequest, supplying the request hash of the user action you want to protect through the setRequestHash method.
  3. Use the integrity token provider to call request(), supplying the StandardIntegrityTokenRequest.

Java

import com.google.android.gms.tasks.Task;

StandardIntegrityTokenProvider integrityTokenProvider;

// See above how to prepare integrityTokenProvider.

// Request integrity token by providing a user action request hash. Can be called
// several times for different user actions.
String requestHash = "2cp24z...";
Task<StandardIntegrityToken> integrityTokenResponse =
    integrityTokenProvider.request(
        StandardIntegrityTokenRequest.builder()
            .setRequestHash(requestHash)
            .build());
integrityTokenResponse
    .addOnSuccessListener(response -> sendToServer(response.token()))
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator RequestIntegrityTokenCoroutine() {
    StandardIntegrityTokenProvider integrityTokenProvider;

    // See above how to prepare integrityTokenProvider.

    // Request integrity token by providing a user action request hash. Can be called
    // several times for different user actions.
    String requestHash = "2cp24z...";
    var integrityTokenOperation = integrityTokenProvider.Request(
      new StandardIntegrityTokenRequest(requestHash)
    );

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenOperation;

    // Check the resulting error code.
    if (integrityTokenOperation.Error != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenOperation.Error);
        yield break;
    }

    // Get the response.
    var integrityToken = integrityTokenOperation.GetResult();
}

Native

/// Create a StandardIntegrityTokenRequest opaque object.
const char* requestHash = ...;
StandardIntegrityTokenRequest* tokenRequest;
StandardIntegrityTokenRequest_create(&tokenRequest);
StandardIntegrityTokenRequest_setRequestHash(tokenRequest, requestHash);

/// Prepare a StandardIntegrityToken opaque type pointer and call
/// StandardIntegrityTokenProvider_request(). Can be called several times for
/// different user actions. See above how to prepare token provider.
StandardIntegrityToken* token;
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_request(tokenProvider, tokenRequest, &token);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityToken_getStatus(token, &token_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_status == INTEGRITY_RESPONSE_COMPLETED)
{
    const char* integrityToken = StandardIntegrityToken_getToken(token);
}
/// ...
/// Remember to free up resources.
StandardIntegrityTokenRequest_destroy(tokenRequest);
StandardIntegrityToken_destroy(token);
StandardIntegrityTokenProvider_destroy(tokenProvider);
StandardIntegrityManager_destroy();

Decrypt and verify the integrity verdict

After you request an integrity verdict, the Play Integrity API provides an encrypted response token. To obtain the device integrity verdicts, you must decrypt the integrity token on Google's servers. To do so, complete these steps:

  1. Create a service account within the Google Cloud project that's linked to your app.
  2. On your app's server, fetch the access token from your service account credentials using the playintegrity scope, and make the following request:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Read the JSON response.

The resulting payload is a plain-text token that contains integrity verdicts.

Automatic replay protection

To mitigate replay attacks, Google Play automatically ensures that each integrity token can't be reused many times. Attempting to repeatedly decrypt the same token will result in empty verdicts.