かずきち。の日記

サーバサイドエンジニアのつぶやき

iOS11から追加になりましたDeviceCheckについて

DeviceCheckフレームワークAPIの追加について

DeviceCheckがそもそもわからない人がいらっしゃると思います。
DeviceCheckとは「この端末でフリートライアルをしたことがあるか?」「この端末で以前支払い処理を行ったことがあるか」をチェックする仕組みになります。
といったサービス開発者サイドの課金情報を司る上で非常に便利な仕様になっています。
アプリで課金をして、料金を回収する上で非常に重要なAPIです。

https://qiita-image-store.s3.amazonaws.com/0/186419/90e61d06-b081-6b76-87b6-3865d3ee6b28.png
引用:http://qiita.com/owen/items/85dff1e45083d2805140

バイスの認識にはUDID,Macアドレスなどの端末固有のIDがあるのですが、Appleの対策によって削除されました。
だからこそ、アプリの引き継ぎは大事なのです。
DeviceCheckは2bitのタイムスタンプをAppleサーバに蓄積します。
ここが二回目のアプリ課金が起こらない秘密です。

再インストールの流れ

まずはクライアント側でgenerateTokenを呼び出し、一時的なトークンを取得します。
次に、このトークンをAPIサーバーに投げます。
サーバー側では秘密鍵を利用してJWT(JSON Web Token)を生成します。
クライアントから受け取ったトークンとトランザクションID、タイムスタンプをJSONエンコードAppleAPIに投げてデバイスに紐づく情報を取得、または設定を行います。
情報を取得した場合はサーバー側で動作を許可するかしないかといった判定を行います。

<?php
require_once "vendor/autoload.php";
use Zenstruck\JWT\Token;
use Zenstruck\JWT\Signer\OpenSSL\ECDSA\ES256;
use \Ramsey\Uuid\Uuid;

//  _POSTからdeviceTokenを受け取り
$deviceToken = (isset($_POST["deviceToken"]) ? $_POST["deviceToken"] : null);

function generateJWT($teamId, $keyId, $privateKeyFilePath) {
    $payload = [
        "iss" => $teamId,
        "iat" => time()
    ];

    $header = [
        "kid" => $keyId
    ];

    $token = new Token($payload, $header);
    return (string)$token->sign(new ES256(), $privateKeyFilePath);
}

function postRequest($url, $jwt, $bodyArray) {
    $body = json_encode($bodyArray);

    $header = [
        "Authorization: Bearer ". $jwt,
        "Content-Type: application/x-www-form-urlencoded",
        "Content-Length: ".strlen($body)
    ];

    $context = [
        "http" => [
            "method"  => "POST",
            "header"  => implode("\r\n", $header),
            "content" => $body
        ]
    ];

    return file_get_contents($url, false, stream_context_create($context));
}

$teamId = "TEAMID";
$keyId = "KEYID";
$privateKeyFilePath = "PRIVATE KEY FILE PATH";
$jwt = generateJWT($teamId, $keyId, $privateKeyFilePath);

//  情報の取得
$body = [
    "device_token" => $deviceToken,
    "transaction_id" => Uuid::uuid4()->toString(),
    "timestamp" => ceil(microtime(true)*1000) // time()だとだめでした
];
postRequest("https://api.development.devicecheck.apple.com/v1/query_two_bits", $jwt, $body);
//  まだ情報を設定していない場合は、"Failed to find bit state"と返されます。
//  設定している場合はこんな形で帰ってきます。
//  {"bit0":true,"bit1":false,"last_update_time":"2017-06"}


//  情報の設定
$body = [
    "device_token" => $deviceToken,
    "transaction_id" => Uuid::uuid4()->toString(),
    "timestamp" => ceil(microtime(true)*1000),
    "bit0" => true,
    "bit1" => false
];

postRequest("https://api.development.devicecheck.apple.com/v1/update_two_bits", $jwt, $body);