多くのユーザーが新しい Android デバイスをセットアップする際に自身の認証情報を管理します。この手動プロセスは難しく、ユーザー エクスペリエンスの低下につながることがよくあります。Google Play 開発者サービスを利用したライブラリである Block Store API は、ユーザー パスワードの保存に伴う複雑さやセキュリティ リスクなしでアプリがユーザー認証情報を保存できるようにすることで、この問題を解決しようとしています。
Block Store API を使用すると、アプリでデータを保存し、そのデータを取得して新しいデバイスでユーザーを再認証できます。こうすることで、ユーザーが新しいデバイスで初めてアプリを開いたときに、ログイン画面を表示する必要がなくなるため、よりシームレスなエクスペリエンスが実現します。
Block Store を使用するメリットは次のとおりです。
- デベロッパー向けの暗号化された認証情報ストレージ ソリューション。可能であれば、認証情報はエンドツーエンドで暗号化されます。
- ユーザー名とパスワードの代わりにトークンを保存します。
- ログインフローをスムーズにする。
- 複雑なパスワードを管理する手間を省くことができます。
- Google がユーザーの本人確認を行います。
始める前に
アプリを準備するには、以下のセクションに示す手順を完了します。
アプリを構成する
プロジェクト レベルの build.gradle
ファイルの buildscript
セクションと allprojects
セクションの両方に Google の Maven リポジトリを含めます。
buildscript {
repositories {
google()
mavenCentral()
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
Block Store API の Google Play 開発者サービスの依存関係をモジュールの Gradle ビルドファイル(通常は app/build.gradle
)に追加します。
dependencies {
implementation 'com.google.android.gms:play-services-auth-blockstore:16.2.0'
}
仕組み
デベロッパーはブロックストアを使用して、最大 16 バイトの配列を保存および復元できます。これにより、現在のユーザー セッションに関する重要な情報を保存し、必要に応じてこの情報を保存することができます。このデータはエンドツーエンドで暗号化でき、Block Store をサポートするインフラストラクチャはバックアップと復元のインフラストラクチャ上に構築されています。
このガイドでは、ユーザーのトークンをブロックストアに保存するユースケースについて説明します。Block Store を使用するアプリがどのように動作するかは、次の手順で確認できます。
- アプリの認証フロー中またはその後のいずれかの時点で、ユーザーの認証トークンをブロックブロック ストアに保存して、後で取得できます。
- トークンはローカルに保存されます。また、可能であればクラウドにバックアップし、エンドツーエンドで暗号化することも可能です。
- ユーザーが新しいデバイスで復元フローを開始すると、データが転送されます。
- 復元フロー中にユーザーがアプリを復元した場合、アプリは新しいデバイスのブロックストアから保存済みトークンを取得できます。
トークンの保存
ユーザーがアプリにログインしたら、そのユーザー用に生成した認証トークンを Block Store に保存できます。このトークンは、エントリあたり最大 4 KB の一意の鍵ペア値を使用して保存できます。
トークンを保存するには、StoreBytesData.Builder
のインスタンスで setBytes()
と 'setKey(/android/reference/com/google/android/gms/auth/blockstore/StoreBytesData.Builder.html#setKey(java.lang.String)' を呼び出して、ユーザーの認証情報をソースデバイスに保存します。Block Store でトークンを保存すると、トークンは暗号化され、デバイスにローカルに保存されます。
次のサンプルは、認証トークンをローカル デバイスに保存する方法を示しています。
Java
BlockstoreClient client = Blockstore.getClient(this); byte[] bytes1 = new byte[] { 1, 2, 3, 4 }; // Store one data block. String key1 = "com.example.app.key1"; StoreBytesData storeRequest1 = StoreBytesData.Builder() .setBytes(bytes1) // Call this method to set the key value pair the data should be associated with. .setKeys(Arrays.asList(key1)) .build(); client.storeBytes(storeRequest1) .addOnSuccessListener(result -> Log.d(TAG, "stored " + result + " bytes")) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this) val bytes1 = byteArrayOf(1, 2, 3, 4) // Store one data block. val key1 = "com.example.app.key1" val storeRequest1 = StoreBytesData.Builder() .setBytes(bytes1) // Call this method to set the key value with which the data should be associated with. .setKeys(Arrays.asList(key1)) .build() client.storeBytes(storeRequest1) .addOnSuccessListener { result: Int -> Log.d(TAG, "Stored $result bytes") } .addOnFailureListener { e -> Log.e(TAG, "Failed to store bytes", e) }
デフォルト トークンを使用する
キーなしで StoreBytes を使用して保存されたデータは、デフォルトのキー BlockstoreClient.DEFAULT_BYTES_DATA_KEY を使用します。
Java
BlockstoreClient client = Blockstore.getClient(this); // The default key BlockstoreClient.DEFAULT_BYTES_DATA_KEY. byte[] bytes = new byte[] { 9, 10 }; StoreBytesData storeRequest = StoreBytesData.Builder() .setBytes(bytes) .build(); client.storeBytes(storeRequest) .addOnSuccessListener(result -> Log.d(TAG, "stored " + result + " bytes")) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this); // the default key BlockstoreClient.DEFAULT_BYTES_DATA_KEY. val bytes = byteArrayOf(1, 2, 3, 4) val storeRequest = StoreBytesData.Builder() .setBytes(bytes) .build(); client.storeBytes(storeRequest) .addOnSuccessListener { result: Int -> Log.d(TAG, "stored $result bytes") } .addOnFailureListener { e -> Log.e(TAG, "Failed to store bytes", e) }
トークンの取得
その後、ユーザーが新しいデバイスで復元フローを実行すると、Google Play 開発者サービスはまずユーザーを確認し、次にブロックストア データを取得します。ユーザーは復元フローの一環としてアプリデータを復元することに同意しているため、追加の同意は必要ありません。ユーザーがアプリを開いたときに、retrieveBytes()
を呼び出して、Block Store からトークンをリクエストできます。取得したトークンを使用して、新しいデバイスでユーザーのログイン状態を維持できます。
次のサンプルは、特定のキーに基づいて複数のトークンを取得する方法を示しています。
Java
BlockstoreClient client = Blockstore.getClient(this); // Retrieve data associated with certain keys. String key1 = "com.example.app.key1"; String key2 = "com.example.app.key2"; String key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY; // Used to retrieve data stored without a key ListrequestedKeys = Arrays.asList(key1, key2, key3); // Add keys to array RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder() .setKeys(requestedKeys) .build(); client.retrieveBytes(retrieveRequest) .addOnSuccessListener( result -> { Map blockstoreDataMap = result.getBlockstoreDataMap(); for (Map.Entry entry : blockstoreDataMap.entrySet()) { Log.d(TAG, String.format( "Retrieved bytes %s associated with key %s.", new String(entry.getValue().getBytes()), entry.getKey())); } }) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this) // Retrieve data associated with certain keys. val key1 = "com.example.app.key1" val key2 = "com.example.app.key2" val key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY // Used to retrieve data stored without a key val requestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array val retrieveRequest = RetrieveBytesRequest.Builder() .setKeys(requestedKeys) .build() client.retrieveBytes(retrieveRequest) .addOnSuccessListener { result: RetrieveBytesResponse -> val blockstoreDataMap = result.blockstoreDataMap for ((key, value) in blockstoreDataMap) { Log.d(ContentValues.TAG, String.format( "Retrieved bytes %s associated with key %s.", String(value.bytes), key)) } } .addOnFailureListener { e: Exception? -> Log.e(ContentValues.TAG, "Failed to store bytes", e) }
すべてのトークンを取得しています。
以下は、BlockStore に保存されているすべてのトークンを取得する方法の例です。
Java
BlockstoreClient client = Blockstore.getClient(this) // Retrieve all data. RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder() .setRetrieveAll(true) .build(); client.retrieveBytes(retrieveRequest) .addOnSuccessListener( result -> { MapblockstoreDataMap = result.getBlockstoreDataMap(); for (Map.Entry entry : blockstoreDataMap.entrySet()) { Log.d(TAG, String.format( "Retrieved bytes %s associated with key %s.", new String(entry.getValue().getBytes()), entry.getKey())); } }) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this) val retrieveRequest = RetrieveBytesRequest.Builder() .setRetrieveAll(true) .build() client.retrieveBytes(retrieveRequest) .addOnSuccessListener { result: RetrieveBytesResponse -> val blockstoreDataMap = result.blockstoreDataMap for ((key, value) in blockstoreDataMap) { Log.d(ContentValues.TAG, String.format( "Retrieved bytes %s associated with key %s.", String(value.bytes), key)) } } .addOnFailureListener { e: Exception? -> Log.e(ContentValues.TAG, "Failed to store bytes", e) }
デフォルトの鍵を取得する方法の例を次に示します。
Java
BlockStoreClient client = Blockstore.getClient(this); RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder() .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY)) .build(); client.retrieveBytes(retrieveRequest);
Kotlin
val client = Blockstore.getClient(this) val retrieveRequest = RetrieveBytesRequest.Builder() .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY)) .build() client.retrieveBytes(retrieveRequest)
トークンの削除
BlockStore からトークンを削除するには、次の理由が考えられます。
- ユーザーのログアウト フローを行います。
- トークンが取り消されたか、無効である。
トークンの取得と同様に、削除が必要なキーの配列を設定して、削除するトークンを指定できます。
特定のキーを削除する例を以下に示します。
Java
BlockstoreClient client = Blockstore.getClient(this); // Delete data associated with certain keys. String key1 = "com.example.app.key1"; String key2 = "com.example.app.key2"; String key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY; // Used to delete data stored without key ListrequestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array DeleteBytesRequest deleteRequest = new DeleteBytesRequest.Builder() .setKeys(requestedKeys) .build(); client.deleteBytes(deleteRequest)
Kotlin
val client = Blockstore.getClient(this) // Retrieve data associated with certain keys. val key1 = "com.example.app.key1" val key2 = "com.example.app.key2" val key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY // Used to retrieve data stored without a key val requestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array val retrieveRequest = DeleteBytesRequest.Builder() .setKeys(requestedKeys) .build() client.deleteBytes(retrieveRequest)
すべてのトークンを削除
次の例では、BlockStore に現在保存されているすべてのトークンを削除します。
Java
// Delete all data. DeleteBytesRequest deleteAllRequest = new DeleteBytesRequest.Builder() .setDeleteAll(true) .build(); client.deleteBytes(deleteAllRequest) .addOnSuccessListener(result -> Log.d(TAG, "Any data found and deleted? " + result));
Kotlin
val deleteAllRequest = DeleteBytesRequest.Builder() .setDeleteAll(true) .build() client.deleteBytes(deleteAllRequest) .addOnSuccessListener { result: Boolean -> Log.d(TAG, "Any data found and deleted? $result") }
エンドツーエンドの暗号化
エンドツーエンドの暗号化を利用できるようにするには、デバイスに Android 9 以降が搭載され、ユーザーがデバイスに画面ロック(PIN、パターン、パスワード)を設定している必要があります。isEndToEndEncryptionAvailable()
を呼び出すことで、デバイスで暗号化が利用可能かどうかを確認できます。
次のサンプルは、クラウドのバックアップ中に暗号化が利用可能かどうかを確認する方法を示しています。
client.isEndToEndEncryptionAvailable()
.addOnSuccessListener { result ->
Log.d(TAG, "Will Block Store cloud backup be end-to-end encrypted? $result")
}
クラウド バックアップを有効にする
クラウド バックアップを有効にするには、StoreBytesData
オブジェクトに setShouldBackupToCloud()
メソッドを追加します。setShouldBackupToCloud()
が true に設定されている場合、Block Store は保存されたバイトを定期的にクラウドにバックアップします。
次のサンプルは、クラウド バックアップがエンドツーエンドで暗号化されている場合にのみ、クラウド バックアップを有効にする方法を示しています。
val client = Blockstore.getClient(this)
val storeBytesDataBuilder = StoreBytesData.Builder()
.setBytes(/* BYTE_ARRAY */)
client.isEndToEndEncryptionAvailable()
.addOnSuccessListener { isE2EEAvailable ->
if (isE2EEAvailable) {
storeBytesDataBuilder.setShouldBackupToCloud(true)
Log.d(TAG, "E2EE is available, enable backing up bytes to the cloud.")
client.storeBytes(storeBytesDataBuilder.build())
.addOnSuccessListener { result ->
Log.d(TAG, "stored: ${result.getBytesStored()}")
}.addOnFailureListener { e ->
Log.e(TAG, “Failed to store bytes”, e)
}
} else {
Log.d(TAG, "E2EE is not available, only store bytes for D2D restore.")
}
}
テスト方法
開発中に次のメソッドを使用して、復元フローをテストします。
同じデバイスのアンインストール/再インストール
ユーザーがバックアップ サービスを有効にしている場合([設定] > [Google] > [バックアップ] で確認できます)、アンインストールと再インストール後もブロックストア データは保持されます。
次の手順に沿ってテストしてください。
- BlockStore API をテストアプリに統合します。
- テストアプリを使用して BlockStore API を呼び出し、データを保存します。
- テストアプリをアンインストールしてから、同じデバイスにアプリを再インストールします。
- テストアプリを使用して BlockStore API を呼び出し、データを取得します。
- 取得されたバイト数が、アンインストール前に保存されたバイト数と同じであることを確認します。
デバイス間
ほとんどの場合、この段階で対象デバイスを出荷時設定にリセットする必要があります。その後、Android ワイヤレス復元フローまたは Google ケーブル復元(サポートされているデバイスの場合)を入力できます。
クラウドの復元
- Blockstore API をテストアプリに統合します。テストアプリは Play ストアに送信する必要があります。
- ソースデバイスで、テストアプリを使用して Blockstore API を呼び出し、shouldBackUpToCloud を true に設定してデータを保存します。
- O 以降のデバイスでは、Block Store クラウド バックアップを手動でトリガーできます。[設定] > [Google] > [バックアップ] に移動し、[今すぐバックアップ] ボタンをクリックしてください。
- Block Store のクラウド バックアップが成功したことを確認するには、次の操作を行います。
- バックアップが終了したら、「CloudSyncBpTkSvc」タグが付いたログ行を検索します。
- 「......, CloudSyncBpTkSvc: sync result: SUCCESS, ..., uploaded size: XXX bytes ...」といった行が表示されます。
- Block Store のクラウド バックアップの後、5 分間のクールダウン期間があります。その 5 分以内に [今すぐバックアップ] ボタンをクリックしても、別の Block Store クラウド バックアップがトリガーされることはありません。
- Block Store のクラウド バックアップが成功したことを確認するには、次の操作を行います。
- 対象のデバイスを出荷時の設定にリセットし、クラウド復元フローを進めます。復元フロー中にテストアプリを復元する場合に選択します。クラウド復元フローの詳細については、サポートされているクラウド復元フローをご覧ください。
- 対象デバイスで、テストアプリを使用して Blockstore API を呼び出し、データを取得します。
- 取得したバイトがソースデバイスに保存されているバイトと同じであることを確認します。
デバイスの要件
エンドツーエンドの暗号化
- エンドツーエンドの暗号化は、Android 9(API 29)以降を搭載しているデバイスでサポートされています。
- エンドツーエンドの暗号化を有効にして、ユーザーのデータを正しく暗号化するには、デバイスで PIN、パターン、またはパスワードによる画面ロックを設定する必要があります。
デバイス間の復元フロー
デバイスを復元するには、ソースデバイスとターゲット デバイスが必要です。データの転送を行うデバイスが 2 台あります。
ソースデバイスをバックアップするには、Android 6(API 23)以降を実行している必要があります。
Android 9(API 29)以降を搭載しているデバイスをターゲットにし、復元できるようにします。
デバイス間の復元フローについて詳しくは、こちらを参照してください。
クラウドのバックアップと復元のフロー
クラウドのバックアップと復元には、ソースデバイスとターゲット デバイスが必要です。
ソースデバイスをバックアップするには、Android 6(API 23)以降を実行している必要があります。
Target デバイスは、それぞれのベンダーに基づいてサポートされています。Google Pixel は Android 9(API 29)からこの機能を使用できます。その他のすべてのデバイスは Android 12(API 31)以降を搭載している必要があります。