Loja de blocos

Muitos usuários ainda gerenciam as próprias credenciais ao configurar um novo dispositivo Android. Esse processo manual pode se tornar desafiador e geralmente resulta em uma experiência do usuário ruim. A API Block Store, uma biblioteca com a tecnologia do Google Play Services, busca resolver isso fornecendo uma maneira para que os apps salvem credenciais de usuário sem a complexidade ou o risco de segurança associado ao salvamento de senhas de usuários.

A API Block Store permite que o app armazene dados que podem ser recuperados posteriormente para autenticar usuários novamente em um novo dispositivo. Isso ajuda a fornecer uma experiência mais tranquila para o usuário, já que ele não precisa ver uma tela de login ao iniciar o app pela primeira vez no novo dispositivo.

Os benefícios de usar o Block Store incluem:

  • Solução de armazenamento de credenciais criptografada para desenvolvedores. As credenciais são criptografadas de ponta a ponta quando possível.
  • Salve tokens em vez de nomes de usuário e senhas.
  • Elimine o atrito dos fluxos de login.
  • Simplifique o gerenciamento de senhas complexas para os usuários.
  • O Google verifica a identidade do usuário.

Antes de começar

Para preparar o app, siga as etapas nas seções a seguir.

Configurar o app

No arquivo build.gradle do projeto, inclua o repositório Maven do Google nas seções buildscript e allprojects:

buildscript {
  repositories {
    google()
    mavenCentral()
  }
}

allprojects {
  repositories {
    google()
    mavenCentral()
  }
}

Adicione a dependência do Google Play Services para a API Block Store ao arquivo de build do Gradle do seu módulo, que geralmente é app/build.gradle:

dependencies {
  implementation 'com.google.android.gms:play-services-auth-blockstore:16.2.0'
}

Como funciona

O Block Store permite que os desenvolvedores salvem e restaurem matrizes de até 16 bytes. Isso permite que você salve informações importantes sobre a sessão do usuário atual e oferece a flexibilidade de salvar essas informações da forma que desejar. Esses dados podem ser criptografados de ponta a ponta, e a infraestrutura compatível com o Block Store é baseada na infraestrutura de backup e restauração.

Este guia aborda o caso de uso de salvar o token de um usuário no Block Store. As etapas a seguir descrevem como funciona um app que usa a Block Store:

  1. Durante o fluxo de autenticação do app ou a qualquer momento depois disso, é possível armazenar o token de autenticação do usuário na Block Store para recuperação posterior.
  2. O token será armazenado localmente e também poderá ser armazenado em backup na nuvem, criptografado de ponta a ponta, quando possível.
  3. Os dados são transferidos quando o usuário inicia um fluxo de restauração em um novo dispositivo.
  4. Se o usuário restaurar o app durante o fluxo de restauração, ele poderá recuperar o token salvo da Block Store no novo dispositivo.

Como salvar o token

Quando um usuário faz login no seu aplicativo, você pode salvar o token de autenticação gerado para ele na Block Store. Você pode armazenar esse token usando um valor exclusivo de par de chaves com no máximo 4 KB por entrada. Para armazenar o token, chame setBytes() e setKey() em uma instância de StoreBytesData.Builder para armazenar as credenciais do usuário no dispositivo de origem. Depois que você salva o token com o Block Store, ele é criptografado e armazenado localmente no dispositivo.

Veja na amostra a seguir como salvar o token de autenticação no dispositivo local:

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)
    }

Usar token padrão

Os dados salvos usando StoreBytes sem uma chave usam a chave padrão 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)
    }

Como recuperar o token

Mais tarde, quando um usuário passar pelo fluxo de restauração em um novo dispositivo, o Google Play Services o verificará primeiro e depois recuperará os dados do Block Store. O usuário já concordou em restaurar os dados do app como parte do fluxo de restauração. Portanto, não é necessário consentimento adicional. Quando o usuário abre o app, é possível solicitar o token do Block Store chamando retrieveBytes(). O token recuperado pode ser usado para manter o usuário conectado no novo dispositivo.

Veja na amostra a seguir como recuperar vários tokens com base em chaves específicas.

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

List requestedKeys = 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)
  }

Como recuperar todos os tokens.

Veja abaixo um exemplo de como recuperar todos os tokens salvos no BlockStore.

Java

BlockstoreClient client = Blockstore.getClient(this)

// Retrieve all data.
RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder()
    .setRetrieveAll(true)
    .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)

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)
  }

Veja abaixo um exemplo de como recuperar a chave padrão.

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)

Como excluir tokens

A exclusão de tokens do BlockStore pode ser necessária pelos seguintes motivos:

  • O usuário passa pelo fluxo de saída do usuário.
  • O token foi revogado ou é inválido.

Assim como na recuperação de tokens, é possível especificar quais tokens precisam ser excluídos definindo uma matriz de chaves que exigem exclusão.

Veja abaixo um exemplo de como excluir determinadas chaves.

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

List requestedKeys = 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)

Excluir todos os tokens

O exemplo abaixo exclui todos os tokens salvos no 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")
  }

Criptografia de ponta a ponta

Para que a criptografia de ponta a ponta seja disponibilizada, o dispositivo precisa ter o Android 9 ou versão mais recente, e o usuário precisa ter definido um bloqueio de tela (PIN, padrão ou senha) para o dispositivo. É possível verificar se a criptografia estará disponível no dispositivo chamando isEndToEndEncryptionAvailable().

Veja na amostra a seguir como verificar se a criptografia estará disponível durante o backup na nuvem:

client.isEndToEndEncryptionAvailable()
        .addOnSuccessListener { result ->
          Log.d(TAG, "Will Block Store cloud backup be end-to-end encrypted? $result")
        }

Ativar backup em nuvem

Para ativar o backup na nuvem, adicione o método setShouldBackupToCloud() ao objeto StoreBytesData. O Block Store fará periodicamente o backup para a nuvem dos bytes armazenados quando setShouldBackupToCloud() for definido como verdadeiro.

O exemplo a seguir mostra como ativar o backup na nuvem somente quando ele é criptografado de ponta a ponta:

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.")
          }
        }

Como testar

Use os métodos a seguir durante o desenvolvimento para testar os fluxos de restauração.

Desinstalação/reinstalação do mesmo dispositivo

Se o usuário ativar os serviços de backup (pode ser conferido em Configurações > Google > Backup), os dados do Block Store permanecerão durante a desinstalação/reinstalação do app.

Siga estas etapas para testar:

  1. Integre a API BlockStore ao app de teste.
  2. Use o app de teste para invocar a API BlockStore e armazenar seus dados.
  3. Desinstale o app de teste e reinstale-o no mesmo dispositivo.
  4. Use o app de teste para invocar a API BlockStore e recuperar seus dados.
  5. Verifique se os bytes recuperados são os mesmos que foram armazenados antes da desinstalação.

De dispositivo a dispositivo

Na maioria dos casos, será necessária uma redefinição para a configuração original do dispositivo de destino. Em seguida, insira o fluxo de restauração sem fio do Android ou a restauração de cabo do Google (para dispositivos compatíveis).

Restauração em nuvem

  1. Integre a API Blockstore ao app de teste. O app de teste precisa ser enviado à Play Store.
  2. No dispositivo de origem, use o app de teste para invocar a API Blockstore e armazenar seus dados, com o "backBackUpToCloud" definido como verdadeiro.
  3. Para dispositivos com Android O e versões posteriores, é possível acionar manualmente um backup na nuvem do Block Store: acesse Configurações > Google > Backup e clique no botão "Fazer backup agora".
    1. Para verificar se o backup na nuvem do Block Store foi concluído, faça o seguinte:
      1. Após a conclusão do backup, procure linhas de registro com a tag "CloudSyncBpTkSvc".
      2. Você verá linhas como esta: “......, CloudSyncBpTkSvc: sync result: SUCCESS, ..., uploaded size: XXX bytes ...”
    2. Depois de um backup na nuvem da Block Store, há um período de cinco minutos. Dentro desses cinco minutos, clicar no botão "Fazer backup agora" não acionará outro backup na nuvem do Block Store.
  4. Redefina o dispositivo de destino para a configuração original e siga um fluxo de restauração para a nuvem. Selecione para restaurar o app de teste durante o fluxo de restauração. Para mais informações sobre fluxos de restauração na nuvem, consulte Fluxos de restauração na nuvem compatíveis.
  5. No dispositivo de destino, use o app de teste para invocar a API Blockstore e recuperar seus dados.
  6. Verifique se os bytes recuperados são os mesmos que foram armazenados no dispositivo de origem.

Requisitos do dispositivo

Criptografia de ponta a ponta

  • A criptografia de ponta a ponta é compatível com dispositivos que executam o Android 9 (API 29) e versões mais recentes.
  • O dispositivo precisa ter um bloqueio de tela definido com um PIN, padrão ou senha para que a criptografia de ponta a ponta seja ativada e criptografe corretamente os dados do usuário.

Fluxo de restauração de dispositivo para dispositivo

A restauração de dispositivo para dispositivo exigirá que você tenha um dispositivo de origem e um dispositivo de destino. Esses serão os dois dispositivos que estão transferindo dados.

Os dispositivos de origem precisam ter o Android 6 (API 23) e versões mais recentes para fazer backup.

Segmente dispositivos com o Android 9 (API 29) e versões posteriores para que possam ser restaurados.

Veja mais informações sobre o fluxo de restauração de um dispositivo para outro neste link.

Fluxo de backup e restauração do Cloud

O backup e a restauração na nuvem exigirão um dispositivo de origem e um dispositivo de destino.

Os dispositivos de origem precisam ter o Android 6 (API 23) e versões mais recentes para fazer backup.

Os dispositivos de segmentação são compatíveis com base nos fornecedores. Os dispositivos Pixel podem usar esse recurso do Android 9 (API 29) e todos os outros dispositivos precisam executar o Android 12 (API 31) ou versões mais recentes.