Bloquear tienda

Muchos usuarios siguen administrando sus propias credenciales cuando configuran un dispositivo Android nuevo. Este proceso manual puede volverse difícil y, a menudo, dar como resultado una mala experiencia del usuario. La API de Block Store, una biblioteca con la tecnología de los Servicios de Google Play, busca resolver este problema proporcionando una forma para que las apps guarden las credenciales del usuario sin la complejidad o el riesgo de seguridad asociado con el guardado de contraseñas del usuario.

La API de Block Store permite que tu app almacene datos que luego puede recuperar para volver a autenticar a los usuarios en un dispositivo nuevo. Esto ayuda a proporcionar una experiencia más fluida para el usuario, ya que no necesita ver una pantalla de acceso cuando inicia la app por primera vez en el dispositivo nuevo.

Los beneficios de usar Block Store incluyen los siguientes:

  • Solución de almacenamiento de credenciales encriptadas para desarrolladores. Cuando es posible, las credenciales se encriptan de extremo a extremo.
  • Guarda tokens en lugar de nombres de usuario y contraseñas.
  • Eliminar los obstáculos de los flujos de acceso
  • Evita que los usuarios se encarguen de administrar contraseñas complejas.
  • Google verifica la identidad del usuario.

Antes de comenzar

Para preparar tu app, completa los pasos que se indican en las siguientes secciones.

Cómo configurar tu app

En el archivo build.gradle de nivel de proyecto, incluye el repositorio de Maven de Google en las secciones buildscript y allprojects:

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

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

Agrega la dependencia de los Servicios de Google Play para la API de Block Store al archivo de compilación de Gradle de tu módulo, que suele ser app/build.gradle:

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

Cómo funciona

Block Store permite a los desarrolladores guardar y restablecer arrays de hasta 16 bytes. Esto te permite guardar información importante sobre la sesión de usuario actual y ofrece la flexibilidad de guardar esta información de la forma que desees. Estos datos se pueden encriptar de extremo a extremo y la infraestructura que admite Block Store se basa en la infraestructura de copia de seguridad y restablecimiento.

En esta guía, se abordará el caso de uso de guardar el token de un usuario en Block Store. En los siguientes pasos, se describe cómo funcionaría una app que usa Block Store:

  1. Durante el flujo de autenticación de tu app, o en cualquier momento posterior, puedes almacenar el token de autenticación del usuario en Block Store para recuperarlo más tarde.
  2. El token se almacenará de forma local y, además, se podrá crear una copia de seguridad en la nube con encriptación de extremo a extremo cuando sea posible.
  3. Los datos se transfieren cuando el usuario inicia un flujo de restablecimiento en un dispositivo nuevo.
  4. Si el usuario restablece tu app durante el flujo de restablecimiento, esta podrá recuperar el token guardado de Block Store en el dispositivo nuevo.

Guarda el token

Cuando un usuario accede a tu app, puedes guardar en Block Store el token de autenticación que generas para ese usuario. Puedes almacenar este token con un valor de par de claves único con un máximo de 4 KB por entrada. Para almacenar el token, llama a setBytes() y setKey() en una instancia de StoreBytesData.Builder para almacenar las credenciales del usuario en el dispositivo de origen. Después de guardar el token con Block Store, este se encripta y se almacena de forma local en el dispositivo.

En el siguiente ejemplo, se muestra cómo guardar el token de autenticación en el 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 predeterminado

Los datos guardados con StoreBytes sin una clave usan la clave predeterminada 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)
    }

Recupera el token

Más adelante, cuando un usuario completa el flujo de restablecimiento en un dispositivo nuevo, los Servicios de Google Play primero verifican al usuario y, luego, recupera los datos de Block Store. El usuario ya aceptó restablecer los datos de tu app como parte del flujo de restablecimiento, por lo que no se requieren consentimientos adicionales. Cuando el usuario abre tu app, puedes solicitar el token desde Block Store llamando a retrieveBytes(). El token recuperado se puede usar para mantener el acceso del usuario en el dispositivo nuevo.

En el siguiente ejemplo, se muestra cómo recuperar varios tokens según claves 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)
  }

Recuperando todos los tokens

A continuación, se muestra un ejemplo de cómo recuperar todos los tokens guardados en 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)
  }

A continuación, se muestra un ejemplo de cómo recuperar la clave predeterminada.

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)

Borra tokens

Es posible que sea necesario borrar tokens de BlockStore por los siguientes motivos:

  • El usuario pasa por el flujo de cierre de su sesión.
  • El token se revocó o no es válido.

De manera similar a la recuperación de tokens, puedes especificar los tokens que deben borrarse. Para ello, debes configurar una matriz de claves que requieran su eliminación.

A continuación, se muestra un ejemplo para borrar determinadas claves.

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)

Borrar todos los tokens

En el siguiente ejemplo, se borran todos los tokens guardados actualmente en 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")
  }

Encriptación de extremo a extremo

Para que la encriptación de extremo a extremo esté disponible, el dispositivo debe ejecutar Android 9 o una versión posterior, y el usuario debe haber configurado un bloqueo de pantalla (PIN, patrón o contraseña) para el dispositivo. Para verificar si la encriptación estará disponible en el dispositivo, llama a isEndToEndEncryptionAvailable().

En el siguiente ejemplo, se muestra cómo verificar si la encriptación estará disponible durante la copia de seguridad en la nube:

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

Habilitar la copia de seguridad en la nube

Para habilitar la copia de seguridad en la nube, agrega el método setShouldBackupToCloud() a tu objeto StoreBytesData. Block Store creará copias de seguridad de los bytes almacenados de forma periódica en la nube cuando setShouldBackupToCloud() se configura como verdadero.

En el siguiente ejemplo, se muestra cómo habilitar la copia de seguridad en la nube solo cuando la copia de seguridad en la nube está encriptada de extremo a extremo:

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

Cómo realizar la prueba

Usa los siguientes métodos durante el desarrollo para probar los flujos de restablecimiento.

Desinstalación y reinstalación del mismo dispositivo

Si el usuario habilita los Servicios de copia de seguridad (puedes verificarlos en Configuración > Google > Copia de seguridad), los datos de Block Store se mantendrán después de desinstalar y reinstalar la app.

Puedes seguir estos pasos para hacer la prueba:

  1. Integra la API de BlockStore a tu app de prueba.
  2. Usa la app de prueba para invocar la API de BlockStore y almacenar tus datos.
  3. Desinstala la app de prueba y vuelve a instalarla en el mismo dispositivo.
  4. Usa la app de prueba para invocar la API de BlockStore y recuperar tus datos.
  5. Verifica que los bytes recuperados sean los mismos que se almacenaron antes de la desinstalación.

Dispositivo a dispositivo

En la mayoría de los casos, deberás restablecer la configuración de fábrica del dispositivo de destino. Luego, puedes ingresar el flujo de restablecimiento inalámbrico de Android o el restablecimiento de cables de Google (para dispositivos compatibles).

Restablecimiento en la nube

  1. Integra la API de Blockstore a tu app de prueba. La app de prueba debe enviarse a Play Store.
  2. En el dispositivo de origen, usa la app de prueba para invocar la API de Blockstore y almacenar tus datos, con shouldBackUpToCloud configurado como verdadero.
  3. Para dispositivos con la versión O y versiones posteriores, puedes activar manualmente una copia de seguridad en la nube de Block Store. Para ello, ve a Configuración > Google > Copia de seguridad y haz clic en el botón “Backup Now”.
    1. Para verificar que la copia de seguridad en la nube de Block Store se haya realizado correctamente, puedes hacer lo siguiente:
      1. Una vez finalizada la copia de seguridad, busca líneas de registro con la etiqueta “CloudSyncBpTkSvc”.
      2. Deberías ver líneas como esta: “......, CloudSyncBpTkSvc: sync result: SUCCESS, ..., upload size: XXX bytes ...”
    2. Después de una copia de seguridad en la nube de Block Store, hay un período de “inactividad” de 5 minutos. Después de ese plazo, si haces clic en el botón “Crear una copia de seguridad ahora”, no se activará otra copia de seguridad en la nube de Block Store.
  4. Restablece la configuración de fábrica del dispositivo de destino y pasa por un flujo de restablecimiento en la nube. Selecciona esta opción para restablecer la app de prueba durante el flujo de restablecimiento. Para obtener más información sobre los flujos de restablecimiento de la nube, consulta Flujos de restablecimiento de la nube compatibles.
  5. En el dispositivo de destino, usa la app de prueba para invocar la API de Blockstore y recuperar tus datos.
  6. Verifica que los bytes recuperados sean los mismos que se almacenaron en el dispositivo de origen.

Requisitos del dispositivo

Encriptación de extremo a extremo

  • La encriptación de extremo a extremo es compatible con dispositivos que ejecutan Android 9 (nivel de API 29) y versiones posteriores.
  • Para que la encriptación de extremo a extremo se habilite y encripte correctamente los datos del usuario, el dispositivo debe tener configurado un bloqueo de pantalla con un PIN, un patrón o una contraseña.

Flujo de restablecimiento de un dispositivo a otro

El restablecimiento de un dispositivo a otro requerirá que tengas un dispositivo de origen y uno de destino. Estos serán los dos dispositivos que están transfiriendo datos.

Los dispositivos de origen deben ejecutar Android 6 (nivel de API 23) y versiones posteriores para crear una copia de seguridad.

Segmenta los dispositivos que ejecutan Android 9 (nivel de API 29) y versiones posteriores para tener la capacidad de restablecer datos.

Puedes obtener más información sobre el flujo de restablecimiento de un dispositivo a otro aquí.

Flujo de copia de seguridad y restablecimiento en la nube

La copia de seguridad y el restablecimiento en la nube requerirán un dispositivo de origen y uno de destino.

Los dispositivos de origen deben ejecutar Android 6 (nivel de API 23) y versiones posteriores para crear una copia de seguridad.

Los dispositivos de destino son compatibles según sus proveedores. Los dispositivos Pixel pueden usar esta función a partir de Android 9 (nivel de API 29) y todos los demás dispositivos deben ejecutar Android 12 (nivel de API 31) o versiones posteriores.