'Decode integrity token using Google PlayIntegrity API

I am trying to implement PlayIntegrity API to my Android app, but I don't know how to decrypt and verify the token using Google's servers.

I followed the documentation up to this point:

Making request to the API

And now I am stuck on making the decode request to googleapis. I don't understand how does this instruction work.

I created a Service Account and I downloaded JSON credentials file and put it into my Laravel project, then I tried this piece of code:

$client = new Client();
$client->setAuthConfig(storage_path('app/integrity_check_account.json'));
$client->addScope(PlayIntegrity::class);
$httpClient = $client->authorize();

$result = $httpClient->request('POST', 'https://playintegrity.googleapis.com/v1/my.package.name', [
    'headers' => ['Content-Type' => 'application/json'],
    'body' => "{ 'integrity_token': 'token' }"
]);

dd($result);

So I having two issues with this code:

  1. Am I adding the scope correctly?
  2. Am I making the request correctly? Because it is not working as I am getting 404 error.


Solution 1:[1]

I finally found the solution to my problem while looking at the source of the PlayIntegrity API from the Google APIs Client Library for PHP.

After importing required dependencies:

composer require google/apiclient:^2.12.1

This is my controller:

<?php

namespace App\Http\Controllers;

use Google\Client;
use Google\Service\PlayIntegrity;
use Google\Service\PlayIntegrity\DecodeIntegrityTokenRequest;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController {
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    public function performCheck(Request $request) {
        $client = new Client();
        $client->setAuthConfig(path/to/your/credentials/json/file.json);
        $client->addScope(PlayIntegrity::PLAYINTEGRITY);
        $service = new PlayIntegrity($client);
        $tokenRequest = new DecodeIntegrityTokenRequest();
        $tokenRequest->setIntegrityToken("TOKEN_HERE");
        $result = $service->v1->decodeIntegrityToken('PACKGE_NAME_HERE', $tokenRequest);
        //check result logic here
    }
}

Solution 2:[2]

Thanks for this post, I found it very helpful.

Even so, I still had some problems decoding the verdict. Here are two problems I ran into, and I wasn't sure whether the problem was with how I was calling the Play Integrity API on the device or how I was decoding the response token.

When you decode the verdict token, if you get an exception with error code 400/Request contains an invalid argument, you probably need to set the Cloud project number with IntegrityTokenRequest_setCloudProjectNumber() (C++).

If you get an exception decoding the token with error code 403/The caller does not have permission, double-check that you've set the correct Google Cloud Project number.

Solution 3:[3]

You have to get access token before calling the Play Integrity API. See below 2 request:

POST /token HTTP/1.1
Accept-Encoding: gzip, deflate
User-Agent: Google-HTTP-Java-Client/1.41.1 (gzip)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: oauth2.googleapis.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: close
Content-Length: 811

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjVhY2Y5NjJkNDExZmZiZDE1NmIxZTE3ODcwY2Y0ZGExYjU0ZmM4MGIiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImV4cCI6MTY0ODc3NjU2OCwiaWF0IjoxNjQ4NzcyOTY4LCJpc3MiOiJwbGF5LWludGVncml0eS1mZG5iLXRlc3RAZmRuYi1wbGF5LWludGVncml0eS10ZXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic2NvcGUiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9hdXRoL3BsYXlpbnRlZ3JpdHkifQ.TQM6UFswVl1oe2JLDiPIjgoEyX89eefegh1EiAd3u8ZvO3STbp7g5rgUBC03_3jH0mLspZ4nbGH7m_8cKaYdKbyVs--P7Um591QU68FJxEvG0Nxr-8mjejo-mL4Z5bxXGVTVnd9n2hkWaBEe7iQ7dcqdkRHXNS1Tg2CcLWbCU1q0pxfAtAEe1mRXj5Y-VYfVl-PiN8Cl4Q8ZEbEAPyBkP-eqSMQcMA0nwhgsmIR4JxRH3zbef20SBuZgm0GBPsngUaseyvni-yjGcTmcyB5Sa1CSQL6-384016G9X7jIytF3fOY1pjl0L-N6KD6JmB4fC6ApDYqQmyZhfb5BD4nsjA

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Vary: Origin
Vary: X-Origin
Vary: Referer
Date: Fri, 01 Apr 2022 00:29:30 GMT
Server: scaffolding on HTTPServer2
Cache-Control: private
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Connection: close
Content-Length: 1083

{"access_token":"ya29.c.b0AXv0zTNFkyzpv-uCAecXsZ8U1TelBGDjRVqBckImapqKoYukyNziQ_zsKecAIns4qjS6UeSiY9bSI3cysPbg7jjeBw63079wuKtsX25yDj83WSK2yzUPKev5MfoyJCyRmRmv-SMHYbqq2qQnn5SZiWM6lNV7hisch_s9JcSe3HmRS-ko9R670ywpgMIvzhADl5tSJlD0xwQyulrNRcJDNkNwzum0e-8........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................","expires_in":3599,"token_type":"Bearer"}

POST /v1/com.example.playinegrity:decodeIntegrityToken HTTP/1.1
Accept-Encoding: gzip, deflate
Authorization: Bearer ya29.c.b0AXv0zTNFkyzpv-uCAecXsZ8U1TelBGDjRVqBckImapqKoYukyNziQ_zsKecAIns4qjS6UeSiY9bSI3cysPbg7jjeBw63079wuKtsX25yDj83WSK2yzUPKev5MfoyJCyRmRmv-SMHYbqq2qQnn5SZiWM6lNV7hisch_s9JcSe3HmRS-ko9R670ywpgMIvzhADl5tSJlD0xwQyulrNRcJDNkNwzum0e-8........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
User-Agent: Google-API-Java-Client/1.33.1 Google-HTTP-Java-Client/1.41.1 (gzip)
x-goog-api-client: gl-java/1.8.0 gdcl/1.33.1 mac-os-x/11.6.2
Content-Type: application/json; charset=UTF-8
Content-Encoding: gzip
Host: playintegrity.googleapis.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: close
Content-Length: 712

[GZIP Content]

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Vary: Origin
Vary: X-Origin
Vary: Referer
Date: Fri, 01 Apr 2022 00:29:33 GMT
Server: ESF
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Cache-Control: private, proxy-revalidate
Connection: close
Content-Length: 649

{
  "tokenPayloadExternal": {
    "requestDetails": {
      "requestPackageName": "com.example.playinegrity",
      "timestampMillis": "1648699890779",
      "nonce": "YWJjZGVmZ2hpajEyMzQ1Njc4OTE="
    },
    "appIntegrity": {
      "appRecognitionVerdict": "UNRECOGNIZED_VERSION",
      "packageName": "com.example.playinegrity",
      "certificateSha256Digest": [
        "JAHNMZrOYvOOVQ40zNWm2e4fTmHIFYGo-_rvgk7vs4o"
      ],
      "versionCode": "1"
    },
    "deviceIntegrity": {
      "deviceRecognitionVerdict": [
        "MEETS_DEVICE_INTEGRITY"
      ]
    },
    "accountDetails": {
      "appLicensingVerdict": "UNEVALUATED"
    }
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 hiddeneyes02
Solution 2 James Fairweather
Solution 3 Will Luo