Play Integrity API サーバ側（PHP）のトークン復号サンプル

[グレンジ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/grenge) 13日目の記事を担当しました、k_kimura_01 と申します。
グレンジでサーバサイドエンジニアをしております。

## Play Integrity APIとは
ざっくりいうと、Androidユーザに対して、正しいアプリ、アカウント、端末でプレイしているかどうかを検証することができるAPIになります。

詳しくは公式ドキュメントをご参照ください。

[Play Integrity API の概要](https://developer.android.com/google/play/integrity/overview?hl=ja)

## 今回やること
Play Integrity APIを利用してクライアント（Android）側で取得したトークンをサーバ側（PHP）で受け取った後の、「トークンを復号して結果を取得する」部分のサンプルになります。
また、2024年12月現在、Play Integrity APIは標準APIリクエストとクラシックAPIリクエストがありますが、今回は標準APIリクエストを想定しています。

## 下準備
GoogleのAPIを利用する共通の部分については基本的に省略させてもらいますが、ざっくり以下を事前に準備しておきます。
- サービスアカウントの秘密鍵（jsonファイル）
- google/apiclient 2.0.0以上
- google/apiclient-services 0.282.0以上
※google/apiclient-servicesはこれより古いと標準APIリクエストで使用するRequestHashのパラメータが取得できないかも

## 実践
```php
<?php

class MyPlayIntegrity
{
    private $client;

    /**
     * コンストラクタ
     */
    public function __construct()
    {
        $this->client = new \Google_Client();
        $this->client->setAuthConfig('private_key.json'); // サービスアカウントの秘密鍵のパス
        $this->client->addScope(\Google_Service_PlayIntegrity::PLAYINTEGRITY);
    }

    /**
     * 検証
     */
    public function verify($token)
    {
        // トークンを復号
        $decodeToken = $this->decodeToken($token);

        if (!is_null($decodeToken)) {
            $tokenPayload = $decodeToken->getTokenPayloadExternal();

            // 判定結果を取得
            // requestDetails
            $requestDetails = $tokenPayload->getRequestDetails();
            // パッケージ名
            $requestPackageName = $requestDetails->getRequestPackageName();
            // リクエストハッシュ
            $requestHash = $requestDetails->getRequestHash();
            // トークンの有効期限
            $timestampMillis = $requestDetails->getTimestampMillis();

            // appIntegrity->appRecognitionVerdict
            $appRecognitionVerdict = $tokenPayload->appIntegrity->appRecognitionVerdict;

            // deviceIntegrity->deviceRecognitionVerdict
            // ※通常は配列だが検証結果によっては空の値が返却されることがある
            $deviceRecognitionVerdict = $tokenPayload->deviceIntegrity->deviceRecognitionVerdict;

            // accountDetails->appLicensingVerdict
            $appLicensingVerdict = $tokenPayload->accountDetails->appLicensingVerdict;
        }
    }

    /**
     * トークンを復号
     */
    private function decodeToken($token)
    {
        $playIntegrityService = new \Google_Service_PlayIntegrity($this->client);

        $requestBody = new \Google_Service_PlayIntegrity_DecodeIntegrityTokenRequest([
            'integrityToken' => $token,
        ]);

        $decodeToken = null;
        try {
            // 復号
            $decodeToken = $playIntegrityService->v1->decodeIntegrityToken('アプリのパッケージ名を入れる', $requestBody);
        } catch (\Exception $e) {
            // 復号で失敗した時の処理など
        }

        return $decodeToken;
    }
}
```
エラー処理など諸々端折ってますが、これで取得できた情報を元に以下公式ドキュメントの各項目の値と見比べて検証をしていく感じになると思います。

[完全性判定の結果](https://developer.android.com/google/play/integrity/verdicts?hl=ja&_gl=1*1gf5zlz*_up*MQ..*_ga*MjA4NjQxNDUxOS4xNzMzNzIwNDk4*_ga_6HH9YJMN9M*MTczMzcyNTIyNC4yLjAuMTczMzcyNTIyNC4wLjAuMTU4ODkwMjYzNQ..)

### 補足
トークンの復号について、標準APIリクエストではこの方法しか記載がないですがGoogleサーバでの復号になる為、1日のPlay Integrity APIリクエスト回数に含まれるようなので注意。
※デフォルトでは1日あたり10000件となっている

[1 日あたりの Play Integrity API リクエスト数を引き上げる](https://developer.android.com/google/play/integrity/setup?hl=ja&_gl=1*dax6vf*_up*MQ..*_ga*MTI4ODU0MDY1MC4xNzMzNzIxMjY2*_ga_6HH9YJMN9M*MTczMzcyMTI2NS4xLjAuMTczMzcyMTI2NS4wLjAuNjE1NjY2NzE3#increase-daily)

## 終わりに
Play Integrity APIのトークン復号方法の例が思いのほか見つからなかったので記事にしてみました。
誰かしらの参考になれば幸いです。