多くのユーザーは、新しい Android デバイスを設定する際に、自分で認証情報を管理しています。この手動プロセスは複雑になることが多く、ユーザー エクスペリエンスの低下につながります。Google Play 開発者サービスを基盤とするライブラリである Block Store API は、ユーザーのパスワードの保存に伴う複雑さやセキュリティ リスクを回避しながら、アプリがユーザーの認証情報を保存できるようにすることで、この問題を解決します。
Block Store API を使用すると、アプリはデータを保存し、後でそのデータを取得して新しいデバイスでユーザーを再認証できます。これにより、ユーザーは新しいデバイスでアプリを初めて起動するときにログイン画面が表示されないため、よりシームレスなエクスペリエンスを実現できます。
Block Store を使用するメリットは次のとおりです。
- デベロッパー向けの暗号化された認証情報ストレージ ソリューション。可能な場合は、認証情報がエンドツーエンドで暗号化されます。
- ユーザー名とパスワードではなくトークンを保存します。
- ログインフローの煩わしさを解消します。
- 複雑なパスワードを管理する手間をユーザーにかけさせません。
- Google がユーザーの ID を検証します。
始める前に
アプリを準備するには、以下のセクションに示す手順を完了します。
アプリを設定する
プロジェクト レベルの build.gradle ファイルで、Google's Maven
リポジトリを buildscript
と allprojects セクションの両方に含めます。
buildscript {
repositories {
google()
mavenCentral()
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
モジュールの Gradle ビルドファイル(通常は app/build.gradle)に Block Store API
用の Google Play 開発者サービスの依存関係を追加します。
dependencies {
implementation 'com.google.android.gms:play-services-auth-blockstore:16.4.0'
}
仕組み
Block Store を使用すると、デベロッパーは最大 16 個のバイト配列を保存して復元できます。これにより、現在のユーザー セッションに関する重要な情報を保存し、この情報を自由に保存できます。このデータはエンドツーエンドで暗号化でき、Block Store をサポートするインフラストラクチャは、バックアップと復元のインフラストラクチャ上に構築されています。
このガイドでは、ユーザーのトークンを Block Store に保存するユースケースについて説明します。 Block Store を利用するアプリの仕組みは次のとおりです。
- アプリの認証フロー中、またはその後いつでも、ユーザーの認証トークンを Block Store に保存して後で取得できます。
- トークンはローカルに保存され、可能な場合はエンドツーエンドで暗号化してクラウドにバックアップすることもできます。
- ユーザーが新しいデバイスで復元フローを開始すると、データが転送されます。
- ユーザーが復元フロー中にアプリを復元すると、アプリは新しいデバイスの Block Store から保存されたトークンを取得できます。
トークンを保存する
ユーザーがアプリにログインすると、そのユーザー用に生成した認証トークンを Block Store に保存できます。このトークンは、エントリごとに最大 4 KB の一意のキーと値のペアを使用して保存できます。トークンを保存するには、
setBytes() と setKey() を
StoreBytesData.Builder のインスタンスで呼び出して、ユーザーの認証情報をソース
デバイスに保存します。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 開発者サービスはまずユーザーを検証し、Block Store
データを取得します。ユーザーは復元フローの一環としてアプリデータの復元に同意しているため、追加の同意は必要ありません。ユーザーがアプリを開くと、
Block Store からトークンをリクエストできます。retrieveBytes()取得したトークンを使用して、新しいデバイスでユーザーのログイン状態を維持できます。
次のサンプルは、特定のキーに基づいて複数のトークンを取得する方法を示しています。
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<String, BlockstoreData> blockstoreDataMap = result.getBlockstoreDataMap(); for (Map.Entry<String, BlockstoreData> 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 -> { Map<String, BlockstoreData> blockstoreDataMap = result.getBlockstoreDataMap(); for (Map.Entry<String, BlockstoreData> 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() retrieve bytes, the keyBlockstoreClient.DEFAULT_BYTES_DATA_KEYcan be used in theRetrieveBytesRequestinstance in order to get your saved data
The following example shows how to retrieve the default key.
Java
End-to-end encryption
In order for end-to-end encryption to be made available, the device must be
running Android 9 or higher, and the user must have set a screen lock
(PIN, pattern, or password) for their device. You can verify if encryption will
be available on the device by calling isEndToEndEncryptionAvailable().
The following sample shows how to verify if encryption will be available during cloud backup:
client.isEndToEndEncryptionAvailable()
.addOnSuccessListener { result ->
Log.d(TAG, "Will Block Store cloud backup be end-to-end encrypted? $result")
}
クラウド バックアップを有効にする
クラウド バックアップを有効にするには、setShouldBackupToCloud() メソッドを
StoreBytesData オブジェクトに追加します。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 > バックアップ で確認できます)、Block Store データはアプリのアンインストール/再インストール後も保持されます。
テストを行うには、次の手順に沿って操作します。
- Block Store API をテストアプリに統合します。
- テストアプリを使用して Block Store API を呼び出し、データを保存します。
- テストアプリをアンインストールし、同じデバイスにアプリを再インストールします。
- テストアプリを使用して Block Store API を呼び出し、データを取得します。
- 取得したバイトが、アンインストール前に保存されたバイトと同じであることを確認します。
デバイス間
ほとんどの場合、対象デバイスを出荷時の設定にリセットする必要があります。その後、Android ワイヤレス復元フローまたはGoogle ケーブル復元(対応デバイスの場合)を開始できます。
クラウドによる復元
- Block Store API をテストアプリに統合します。テストアプリは Google Play ストアに送信する必要があります。
- ソースデバイスで、テストアプリを使用して Block Store 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 クラウド バックアップが成功したことを確認するには、次の操作を行います。
- 対象デバイスを出荷時の設定にリセットし、クラウド復元フローを行います。復元フロー中にテストアプリを復元するように選択します。クラウド復元フローの詳細については、サポートされているクラウド復元フローをご覧ください。
- 対象デバイスで、テストアプリを使用して Block Store API を呼び出し、データを取得します。
- 取得したバイトが、ソースデバイスに保存されているバイトと同じであることを確認します。
デバイスの要件
エンドツーエンドの暗号化
- エンドツーエンドの暗号化は、Android 9(API 29)以降を搭載しているデバイスでサポートされています。
- エンドツーエンドの暗号化を有効にしてユーザーのデータを正しく暗号化するには、デバイスに PIN、パターン、またはパスワードで画面ロックを設定する必要があります。
デバイス間の復元フロー
デバイス間の復元では、ソースデバイスと対象のデバイスが必要です。これらは、データを転送する 2 つのデバイスになります。
ソース デバイスは、バックアップを行うために Android 6(API 23)以降を搭載している必要があります。
Android 9(API 29)以降を搭載している対象 デバイスは、復元できます。
デバイス間の復元フローの詳細については、こちらをご覧ください。
クラウド バックアップと復元のフロー
クラウド バックアップと復元には、ソースデバイスと対象デバイスが必要です。
ソース デバイスは、バックアップを行うために Android 6(API 23)以降を搭載している必要があります。
対象 デバイスは、ベンダーに基づいてサポートされています。Google Pixel デバイスは Android 9(API 29)以降でこの機能を使用できます。他のすべてのデバイスは Android 12(API 31)以降を搭載している必要があります。