Decode integrity token using Google PlayIntegrity API
Asked Answered
E

6

8

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.
Ebeneser answered 18/3, 2022 at 11:26 Comment(2)
Your request body does not look like a valid JSON document (single instead of double quotes)Cohin
@Cohin this was never the issue.Ebeneser
E
5

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);
        
        //Integrity check logic below

        //check with old nonce that you need to save somewhere
        if ($oldNonce !== $resultNonce) {
           echo "bad nonce";
           exit(1);
        }

        $deviceVerdict = $result->deviceIntegrity->deviceRecognitionVerdict;
        $appVerdict = $result->appIntegrity->appRecognitionVerdict;
        $accountVerdict = $result->accountDetails->appLicensingVerdict;
   
        //Possible values of $deviceVerdict[0] : MEETS_BASIC_INTEGRITY, MEETS_DEVICE_INTEGRITY, MEETS_STRONG_INTEGRITY
        if (!isset($deviceVerdict) || $deviceVerdict[0] !== 'MEETS_DEVICE_INTEGRITY') {
              echo "device doesn't meet requirement";
              exit(1);
        }

       //Possible values of $appVerdict: PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, UNEVALUATED
        if ($appVerdict !== 'PLAY_RECOGNIZED') {
            echo "App not recognized";
            exit(1);
        }

       //Possible values of $accountVerdict: LICENSED, UNLICENSED, UNEVALUATED
       if ($accountVerdict !== 'LICENSED') {
           echo "User is not licensed to use app";
           exit(1);
       }
    }
}

Possible return verdicts are explained here.

Ebeneser answered 18/3, 2022 at 14:41 Comment(8)
did you manage to get this working? I'm struggling with the decodeIntegrityToken request - not the same error you was receiving but a 400 BAD REQUEST with status INVALID_ARGUMENT. Did you bumped into this any time during your debug? My body contains a "integrity_token" with the value I've received from the client-side flow. ThanksEndurable
Yes it is working fine for me.INVALID_ARGUMENT may occur if you forget to put your token or package name. It also will occur if your token is badly formatted. Also the nonce needs to be base64 encoded with NO_PADDING, NO_WRAPPING and URL_SAFE.Ebeneser
@hiddeneyes02 Where are you generating Nonce ? In app code or at back end ?Superhighway
@Superhighway You generate it from server side. In my case, when a user request something from my API, if the user was not authorized before, a nonce will be generated (and saved in the server's database) and sent back to app to initiate an Integrity Check. The nonce is then sent to Google Play to include in the crypted token answer, which is transferred back to my server, then my server sends it back to Google Play API which returns decrypted message with the original nonce, then I finally check the returned nonce with the one saved in my database. Nonce mismatch means something wrong occurred.Ebeneser
I get a json in result first so for example $result->deviceIntegrity is null, despite a good reply. Example $result: NOTE: This is for a local development install: {"tokenPayloadExternal":{"requestDetails":"nonce":"mynoncebase64","requestPackageName":"mypackage","timestampMillis":"1655543757126"},"appIntegrity":{"appRecognitionVerdict":"UNRECOGNIZED_VERSION","certificateSha256Digest":["somecertdata"],"packageName":"mypackage","versionCode":"myversion"}, "deviceIntegrity":{"deviceRecognitionVerdict":["MEETS_DEVICE_INTEGRITY"]}, "accountDetails":{"appLicensingVerdict":"UNLICENSED"} }}Bugle
May I also suggest the following composer.json to only load needed files: { "require": { "google/apiclient": "^2.7" }, "scripts": { "post-update-cmd": "Google_Task_Composer::cleanup" }, "extra": { "google/apiclient-services": [ "PlayIntegrity" ] } }Bugle
@hiddeneyes02 While executing decode integrity token, I am getting 403 Forbidden with reason "You are not authorized to decode the requested integrity token." Have u by any chance gone through this while your debug?Joniejonina
@UrvashiSoni Some setup is also required from your Google Play console. Go to Setup -> App Integrity and complete required steps.Ebeneser
S
8

I have spent hours to get it work with node js. Sometimes Google is very terrible to document/explain and check its own code.

So I post this for anyone who is looking for integrity decryption with a node js server. The only example I could found is directly inside the node playintegrity module of googleapis. Based on this example here my working code:

async function getAppToken() {

  const auth = new google.auth.GoogleAuth({
    keyFile: 'secret.json',
    scopes: ['https://www.googleapis.com/auth/playintegrity'],
  });

  const authClient = await auth.getClient();

  google.options({auth: authClient});

  const res = await playintegrity.decodeIntegrityToken (
  {
    packageName: 'com.example.myapp',
    requestBody:
        {
        "integrityToken": "myToken"
        }
    }
  );


  console.log(res.data);

  return res.data;
}
 

You can call that function like this

    getAppToken()
    .then(data => {
        console.log(data);
    })
   .catch(e => {
        console.error(e);
        throw e;
    });

And here we go! Hum... no, hold on. You also have to fix the integrity api. Go to your node project and find the v1.js file in the playintegrity module

it should be here: \node_modules\googleapis\build\src\apis\playintegrity

Now open it and add this line in the Playintegrity constructor

this.decodeIntegrityToken = this.v1.decodeIntegrityToken;

To get that

class Playintegrity {
    constructor(options, google) {
        this.context = {
            _options: options || {},
            google,
        };
        this.v1 = new Resource$V1(this.context);
        this.decodeIntegrityToken = this.v1.decodeIntegrityToken;
    }
}

Now it should work

Stipulation answered 9/6, 2022 at 20:31 Comment(4)
I can confirm this works. Remember to add const playintegrity = google.playintegrity('v1');Mahayana
I figured it out without editing node_modules: playintegrity.v1.decodeIntegrityToken (the .v1.). Even their sample code is wrong!!!Thorazine
I want to add like 100 times for "Google is terrible to document"Incudes
Not sure if the Node.js googleapis package has changed, by I got this to work by building an integrity object like so: const playintegrity = google.playintegrity('v1'); const res = await playintegrity.v1.decodeIntegrityToken(params)Daloris
E
5

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);
        
        //Integrity check logic below

        //check with old nonce that you need to save somewhere
        if ($oldNonce !== $resultNonce) {
           echo "bad nonce";
           exit(1);
        }

        $deviceVerdict = $result->deviceIntegrity->deviceRecognitionVerdict;
        $appVerdict = $result->appIntegrity->appRecognitionVerdict;
        $accountVerdict = $result->accountDetails->appLicensingVerdict;
   
        //Possible values of $deviceVerdict[0] : MEETS_BASIC_INTEGRITY, MEETS_DEVICE_INTEGRITY, MEETS_STRONG_INTEGRITY
        if (!isset($deviceVerdict) || $deviceVerdict[0] !== 'MEETS_DEVICE_INTEGRITY') {
              echo "device doesn't meet requirement";
              exit(1);
        }

       //Possible values of $appVerdict: PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, UNEVALUATED
        if ($appVerdict !== 'PLAY_RECOGNIZED') {
            echo "App not recognized";
            exit(1);
        }

       //Possible values of $accountVerdict: LICENSED, UNLICENSED, UNEVALUATED
       if ($accountVerdict !== 'LICENSED') {
           echo "User is not licensed to use app";
           exit(1);
       }
    }
}

Possible return verdicts are explained here.

Ebeneser answered 18/3, 2022 at 14:41 Comment(8)
did you manage to get this working? I'm struggling with the decodeIntegrityToken request - not the same error you was receiving but a 400 BAD REQUEST with status INVALID_ARGUMENT. Did you bumped into this any time during your debug? My body contains a "integrity_token" with the value I've received from the client-side flow. ThanksEndurable
Yes it is working fine for me.INVALID_ARGUMENT may occur if you forget to put your token or package name. It also will occur if your token is badly formatted. Also the nonce needs to be base64 encoded with NO_PADDING, NO_WRAPPING and URL_SAFE.Ebeneser
@hiddeneyes02 Where are you generating Nonce ? In app code or at back end ?Superhighway
@Superhighway You generate it from server side. In my case, when a user request something from my API, if the user was not authorized before, a nonce will be generated (and saved in the server's database) and sent back to app to initiate an Integrity Check. The nonce is then sent to Google Play to include in the crypted token answer, which is transferred back to my server, then my server sends it back to Google Play API which returns decrypted message with the original nonce, then I finally check the returned nonce with the one saved in my database. Nonce mismatch means something wrong occurred.Ebeneser
I get a json in result first so for example $result->deviceIntegrity is null, despite a good reply. Example $result: NOTE: This is for a local development install: {"tokenPayloadExternal":{"requestDetails":"nonce":"mynoncebase64","requestPackageName":"mypackage","timestampMillis":"1655543757126"},"appIntegrity":{"appRecognitionVerdict":"UNRECOGNIZED_VERSION","certificateSha256Digest":["somecertdata"],"packageName":"mypackage","versionCode":"myversion"}, "deviceIntegrity":{"deviceRecognitionVerdict":["MEETS_DEVICE_INTEGRITY"]}, "accountDetails":{"appLicensingVerdict":"UNLICENSED"} }}Bugle
May I also suggest the following composer.json to only load needed files: { "require": { "google/apiclient": "^2.7" }, "scripts": { "post-update-cmd": "Google_Task_Composer::cleanup" }, "extra": { "google/apiclient-services": [ "PlayIntegrity" ] } }Bugle
@hiddeneyes02 While executing decode integrity token, I am getting 403 Forbidden with reason "You are not authorized to decode the requested integrity token." Have u by any chance gone through this while your debug?Joniejonina
@UrvashiSoni Some setup is also required from your Google Play console. Go to Setup -> App Integrity and complete required steps.Ebeneser
P
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"
    }
  }
}
Photochromy answered 25/4, 2022 at 3:11 Comment(3)
Which token have you passed in assertion field in /token API?Onerous
It is a pain JWT which is signed by your service account key.Photochromy
any reference to these apis please?Uppercut
S
2

If you developing an application (API service) using java code then the below code will send the integrity token to the google server hence you can verify the response. Enable PlayIntegrity API in Google Cloud Platform against the app and download the JSON file and configure in the code. Similarly, you should enable PlayIntegrity API in Google PlayConsole against the app

Add Google Play Integrity Client Library to your project

Maven Dependency

<project>
 <dependencies>
   <dependency>
     <groupId>com.google.apis</groupId>
     <artifactId>google-api-services-playintegrity</artifactId>
     <version>v1-rev20220211-1.32.1</version>
   </dependency>
 </dependencies>

Gradle

repositories {
   mavenCentral()
}
dependencies {
   implementation 'com.google.apis:google-api-services-playintegrity:v1-rev20220211-1.32.1'
}

Token decode

DecodeIntegrityTokenRequest requestObj = new DecodeIntegrityTokenRequest();
requestObj.setIntegrityToken(request.getJws());
//Configure downloaded Json file
GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream("<Path of JSON file>\\file.json"));
HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials);

 HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
 JsonFactory JSON_FACTORY = new JacksonFactory();
 GoogleClientRequestInitializer initialiser = new PlayIntegrityRequestInitializer();
 
 
Builder playIntegrity = new PlayIntegrity.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer).setApplicationName("testapp")
        .setGoogleClientRequestInitializer(initialiser);
             PlayIntegrity play = playIntegrity.build();
    
DecodeIntegrityTokenResponse response = play.v1().decodeIntegrityToken("com.test.android.integritysample", requestObj).execute();

Then the response will be as follows

{
"tokenPayloadExternal": {
    "accountDetails": {
        "appLicensingVerdict": "LICENSED"
    },
    "appIntegrity": {
        "appRecognitionVerdict": "PLAY_RECOGNIZED",
        "certificateSha256Digest": ["pnpa8e8eCArtvmaf49bJE1f5iG5-XLSU6w1U9ZvI96g"],
        "packageName": "com.test.android.integritysample",
        "versionCode": "4"
    },
    "deviceIntegrity": {
        "deviceRecognitionVerdict": ["MEETS_DEVICE_INTEGRITY"]
    },
    "requestDetails": {
        "nonce": "SafetyNetSample1654058651834",
        "requestPackageName": "com.test.android.integritysample",
        "timestampMillis": "1654058657132"
    }
}
}

Check for License

String licensingVerdict = response.getTokenPayloadExternal().getAccountDetails().getAppLicensingVerdict();
    if(!licensingVerdict.equalsIgnoreCase("LICENSED")) {
         throw new Exception("Licence is not valid.");
            
    }

Verify App Integrity

public void checkAppIntegrity(DecodeIntegrityTokenResponse response,  String appId) throws Exception {
    AppIntegrity appIntegrity = response.getTokenPayloadExternal().getAppIntegrity();
    
    if(!appIntegrity.getAppRecognitionVerdict().equalsIgnoreCase("PLAY_RECOGNIZED")) {
        throw new Exception("The certificate or package name does not match Google Play records.");
    }
     if(!appIntegrity.getPackageName().equalsIgnoreCase(appId)) {
         throw new Exception("App package name mismatch.");
        
     }
     
     if(appIntegrity.getCertificateSha256Digest()!= null) {
        //If the app is deployed in Google PlayStore then Download the App signing key certificate from Google Play Console (If you are using managed signing key). 
        //otherwise download Upload key certificate and then find checksum of the certificate.
         Certificate cert = getCertificate("<Path to Signing certificate>\deployment_cert.der");
         MessageDigest md = MessageDigest.getInstance("SHA-256"); 

        byte[] der = cert.getEncoded(); 
        md.update(der);
        byte[] sha256 = md.digest();
        
        //String checksum = Base64.getEncoder().encodeToString(sha256);
        String checksum = Base64.getUrlEncoder().encodeToString(sha256);
        /** Sometimes checksum value ends with '=' character, you can avoid this character before perform the match **/
         checksum  = checksum.replaceAll("=","");         
        if(!appIntegrity.getCertificateSha256Digest().get(0).contains(checksum)) {
             throw new Exception("App certificate mismatch.");
        }
     }
}
public static Certificate getCertificate(String certificatePath)
        throws Exception {
    CertificateFactory certificateFactory = CertificateFactory
            .getInstance("X509");
    FileInputStream in = new FileInputStream(certificatePath);

    Certificate certificate = certificateFactory
            .generateCertificate(in);
    in.close();

    return certificate;
}

Verify Device integrity

//Check Device Integrity
public void deviceIntegrity(DecodeIntegrityTokenResponse response) {
    DeviceIntegrity deviceIntegrity = response.getTokenPayloadExternal().getDeviceIntegrity();
    if(!deviceIntegrity.getDeviceRecognitionVerdict().contains("MEETS_DEVICE_INTEGRITY")) {
        throw new Exception("Does not meet Device Integrity.");
        
    }
}

Similarly you can verify the Nonce and App Package name with previously stored data in the server

Sarah answered 1/6, 2022 at 17:19 Comment(0)
C
1

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.

Clambake answered 6/4, 2022 at 20:49 Comment(0)
P
1

Minor tweeks are needed to make Steeve's (excellent!) snippet work:

const { google } = require("googleapis");

async function validateToken(integrityToken) {
  const auth = new google.auth.GoogleAuth({
    keyFilename: "THIS_IS_THE_SERVICE_ACCOUNT_KEY_IN_JSON_FORMAT.json",
    scopes: ["https://www.googleapis.com/auth/playintegrity"],
  });

  const authClient = await auth.getClient();

  google.options({ auth: authClient });

  const api = google.playintegrity({ version: "v1" });

  const res = await api.v1.decodeIntegrityToken({
    packageName: "YOUR.APP.PACKAGE.NAME",
    requestBody: {
      integrityToken: integrityToken,
    },
  });

  console.log(res.data);
}

validateToken(
  "TOKEN"
);

The part that you need to put in your Android app, something like that should have been added here: https://developer.android.com/google/play/integrity/setup#integrate-into-app , here's a blog post that shows you how to start: https://android-developers.googleblog.com/2022/05/boost-security-of-your-app-with-nonce.html

fun initializePlayIntegrityApi(context: Context) {
    val nonce: String = "So, safe and nothing to worry about. ." <- hardcoding nonce is very bad idea, the whole point is to generate something unique

    val base64Nonce = Base64.encodeToString(
        nonce.encodeToByteArray(),
        Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP
    )

// Create an instance of a manager.
    val integrityManager =
        IntegrityManagerFactory.create(context)

// Request the integrity token by providing a nonce.
    val integrityTokenResponse: Task<IntegrityTokenResponse> =
        integrityManager.requestIntegrityToken(
            IntegrityTokenRequest.builder()
                .setNonce(base64Nonce)
                .setCloudProjectNumber(YOUR_CLOUD_PROJECT_NUMBER)
                .build()
        )

    integrityTokenResponse.addOnCompleteListener {
        if (it.isSuccessful) {
            Log.d("YAYAYA", "success")
            Log.d("YAYAYA", it.toString())
            Log.d("YAYAYA", it.result.toString())
            Log.d("YAYAYA", it.result.token())
        } else {
            Log.d("YAYAYA", "exception")
            Log.d("YAYAYA", it.exception.toString())
        }
    }
}
Potentiate answered 13/3, 2023 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.