Implementa la función Restore Credentials con el Administrador de credenciales

En esta página, se describe cómo crear, acceder y borrar una clave de restablecimiento.

Compatibilidad de versiones

La función Restore Credentials de Credential Manager funciona en dispositivos que ejecutan Android 9 (nivel de API 28) y versiones posteriores, la versión principal 242200000 (v24.22) o posterior de los Servicios de Google Play (GMS), y la versión 1.5.0 o posterior de la biblioteca de androidx.credentials. En dispositivos sin los Servicios de Google Play o con una versión anterior de GMS, detecta CreateCredentialProviderConfigurationException y GetCredentialProviderConfigurationException para que tu app recurra correctamente al acceso estándar.

Requisitos previos

Configura un servidor de la parte de confianza de WebAuthn similar al servidor para las llaves de acceso. Si ya tienes un servidor configurado para controlar la autenticación con llaves de acceso, puedes volver a usar los mismos extremos de registro y aserción de WebAuthn para las claves de restablecimiento.

Dependencias

Agrega las siguientes dependencias al archivo build.gradle del módulo de tu app:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha02"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
}

Restore Credentials se encuentra disponible en la versión 1.5.0 y posteriores de la biblioteca de androidx.credentials. Sin embargo, se recomienda usar las versiones estables más recientes de las dependencias siempre que sea posible.

Cómo afecta android:allowBackup a Restore Credentials

El atributo de manifiesto android:allowBackup controla la copia de seguridad de los datos de la app, que interactúa con Restore Credentials de tres maneras distintas:

  • Transferencia de claves de restablecimiento (independiente de allowBackup): Los Servicios de Google Play transfieren el par de claves RestoreCredential con una copia de seguridad en la nube encriptada de extremo a extremo o una transferencia local de un dispositivo a otro (D2D), independientemente de si android:allowBackup es true o false. En el caso de las transferencias de D2D, la clave de restablecimiento siempre se transfiere. La única condición en la que no se transfiere es durante una copia de seguridad en la nube cuando el usuario inhabilitó la copia de seguridad en la nube en la configuración de Android.
  • Restablecimiento en segundo plano en BackupAgent (requiere android:allowBackup="true"): El acceso silencioso en segundo plano antes de que el usuario abra la app en el dispositivo nuevo (BackupAgent.onRestoreFinished()) solo se ejecuta cuando android:allowBackup="true" y android:backupAgent están configurados en tu manifiesto. Si es android:allowBackup="false", restablece la sesión cuando el usuario inicie tu Activity principal por primera vez. También puedes implementar ambos enfoques para que la restauración sea más sólida.
  • Pruebas de copia de seguridad en la nube de Android Studio (requiere android:allowBackup="true"): Para probar el tipo de copia de seguridad en la nube en la herramienta de copia de seguridad y restablecimiento de Android Studio, se requiere android:allowBackup="true", mientras que las pruebas de dispositivo a dispositivo funcionan independientemente de allowBackup.

Descripción general

Restablecer credenciales sigue un ciclo de vida de tres etapas en los dispositivos anteriores y nuevos del usuario:

  1. Crea una clave de restablecimiento en el dispositivo anterior: Después de que el usuario acceda (o cuando se inicie la app si el usuario ya accedió y no se registra ninguna clave de restablecimiento en el almacenamiento local), recupera las opciones de creación de tu servidor de la parte que confía y llama a credentialManager.createCredential().
  2. Accede con la clave de restablecimiento en el dispositivo nuevo: Recupera la RestoreCredential transferida con una estrategia de dos niveles: primero, de forma silenciosa en BackupAgent.onRestoreFinished() (cuando android:allowBackup="true") para que la sincronización en segundo plano y las notificaciones push se reanuden de inmediato y, segundo, como resguardo en tu Activity.onCreate() de selector en el primer inicio de la app.
  3. Borra la clave de restablecimiento cuando se salga de la cuenta: Cuando el usuario salga de su cuenta o el servidor invalide su sesión, llama a credentialManager.clearCredentialState() con ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) y borra tu marca de sincronización local.

Crea una clave de restablecimiento

Tu app debe abarcar todos los casos de acceso de un usuario para garantizar que los usuarios activos tengan una clave de restablecimiento creada. Crea la clave de restablecimiento en las siguientes situaciones:

  • Si el usuario accedió y aún no se creó una clave de restablecimiento (como en el método onCreate() de la Activity principal).
  • Cuando el usuario accede o completa un flujo de registro de cuenta nueva

Para evitar llamar a createCredential() cada vez que se inicia la app, almacena una marca de sincronización local, como has_synced_restore_credential, en el almacenamiento local. No almacenes esta marca en la copia de seguridad predeterminada de SharedPreferences o DataStore, a menos que ese archivo se excluya en data_extraction_rules.xml; de lo contrario, la Copia de seguridad automática copiará has_synced_restore_credential = true en el dispositivo nuevo y evitará que este genere o rote su propia clave de restablecimiento.

Crea una instancia del Administrador de credenciales

Usa el contexto de la actividad de tu app para crear una instancia de un objeto CredentialManager.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

Obtén opciones de creación de credenciales del servidor de tu app

Usa una biblioteca compatible con FIDO en tu servidor de apps para enviar a tu app cliente el JSON de PublicKeyCredentialCreationOptions necesario para crear la credencial de restablecimiento. Para obtener más información sobre la implementación del servidor, consulta la guía del servidor.

El JSON de las opciones de creación sigue la misma estructura que el registro de llaves de acceso:

{
  "challenge": "<base64url-encoded challenge>",
  "rp": {
    "name": "<relying party name>",
    "id": "<relying party host name>"
  },
  "user": {
    "id": "<base64url-encoded user ID>",
    "name": "<user name>",
    "displayName": "<user display name>"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "attestation": "none",
  "excludeCredentials": [
    {
        "id": "<base64url-encoded credential ID to exclude>", 
        "type": "public-key"
    }
  ],
  "authenticatorSelection": {
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Asegúrate de que el JSON de las opciones de creación cumpla con estos requisitos:

  • challenge y user.id: Codifica ambos valores como cadenas Base64URL sin relleno (reemplaza + por -, / por _ y quita el = final). Los caracteres Base64 estándar o los IDs de números enteros sin procesar causan CreateRestoreCredentialDomException o IllegalArgumentException.
  • rp.id: Se establece en el dominio de la parte que confía (por ejemplo, example.com).
  • pubKeyCredParams: Incluye {"type": "public-key", "alg": -7} (ES256) y {"type": "public-key", "alg": -257} (RS256).

Crea la clave de restablecimiento

Después de recibir el JSON de opciones de creación de claves públicas de tu servidor, une el JSON en un objeto CreateRestoreCredentialRequest y llama a createCredential() en el objeto CredentialManager.

// requestJson contains the credential creation options sent by the server
val createRestoreRequest = CreateRestoreCredentialRequest(requestJson)
val response = try {
    credentialManager.createCredential(context, createRestoreRequest)
} catch (e: E2eeUnavailableException) {
    // Fall back to local device-to-device (D2D) restore if cloud E2EE is unavailable
    credentialManager.createCredential(
        context,
        CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false),
    )
}

Puntos clave sobre el código

  • El objeto CreateRestoreCredentialRequest contiene los siguientes campos:

    • requestJson: Son las opciones de creación de credenciales que envía el servidor de la app en el formato de la API de Web Authentication para PublicKeyCredentialCreationOptionsJSON.
    • isCloudBackupEnabled: Es el campo Boolean para determinar si se debe crear una copia de seguridad de la clave de restablecimiento en la nube. De forma predeterminada, esta marca es true. Este campo tiene los siguientes valores:

      • true: (Recomendado) Este valor habilita la copia de seguridad de las claves de restablecimiento en la nube si el usuario tiene habilitada la copia de seguridad de Google y la encriptación de extremo a extremo, como un bloqueo de pantalla.
      • false: Este valor guarda la clave de forma local y no en la nube. La clave no está disponible en el dispositivo nuevo si el usuario elige restablecer desde la nube, pero se transfiere durante una configuración local de dispositivo a dispositivo (D2D) por cable o Wi-Fi.

Controla la respuesta de creación de credenciales

La API de Credential Manager devuelve una respuesta de tipo CreateRestoreCredentialResponse. Esta respuesta contiene la respuesta de registro de credenciales de clave pública en formato JSON.

Envía response.registrationResponseJson desde tu app al servidor de la parte que confía para verificar y almacenar la clave pública junto con la cuenta del usuario y, luego, registra el marcador de sincronización local en el almacenamiento local. Para obtener más información sobre la verificación del servidor, consulta la guía para llaves de acceso.

Durante el proceso de creación de la clave de restablecimiento, controla estas excepciones:

  • CreateRestoreCredentialDomException: Esta excepción se produce si requestJson no es válido y no sigue el formato de WebAuthn para PublicKeyCredentialCreationOptionsJSON.
  • E2eeUnavailableException: Esta excepción se produce si isCloudBackupEnabled es true, pero el dispositivo del usuario no tiene copia de seguridad de los datos ni encriptación de extremo a extremo, como un bloqueo de pantalla.
    Para garantizar que se creen credenciales de restablecimiento para las transferencias locales de dispositivo a dispositivo (D2D) incluso cuando la E2EE en la nube no esté disponible, detecta E2eeUnavailableException y vuelve a intentar createCredential() de inmediato con CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false).
  • IllegalArgumentException: Esta excepción se produce si requestJson está vacío, no es un JSON válido o no tiene un user.id codificado en Base64URL válido que cumpla con las especificaciones de WebAuthn.

Cómo acceder con una clave de restablecimiento

Usa Restore Credentials para que el usuario acceda de forma silenciosa durante el proceso de configuración del dispositivo.

Obtén opciones de recuperación de credenciales del servidor de la app

Antes de llamar a getCredential() en el dispositivo nuevo, solicita un desafío JSON de WebAuthn PublicKeyCredentialRequestOptions desde el servidor de tu app. Como la app en el dispositivo nuevo aún no sabe qué usuario está accediendo, este extremo debe estar sin autenticar (detección sin usuario). Para obtener orientación relacionada con el servidor, consulta Accede con una llave de acceso y la guía de autenticación del servidor.

El JSON de las opciones de recuperación tiene la siguiente estructura:

{
  "challenge": "<your app challenge>",
  "allowCredentials": [],
  "rpId": "<your app server domain>"
}

Configura el JSON de opciones de recuperación en tu servidor de la siguiente manera:

  • allowCredentials: Omite este campo o pasa un array vacío ([]) porque la cuenta del usuario no se conoce antes del restablecimiento. Después de que se devuelva getCredential(), tu servidor identificará al usuario desde el campo response.userHandle (que coincide con el user.id registrado durante la creación) y buscará la clave pública por id (credentialId).
  • userVerification: Se establece en "discouraged" (o "preferred"). Debido a que Restore Credentials se ejecuta de forma silenciosa sin la IU de bloqueo de pantalla o biométrica, Android anula "required" a "discouraged", y los datos del autenticador que se devuelven a tu servidor tienen la marca Usuario verificado (uv) establecida en false. Configura el verificador del servidor de WebAuthn para que acepte uv = false para las aserciones de claves de restablecimiento.

Obtén la clave de restablecimiento

Para obtener la clave de restablecimiento en el dispositivo nuevo, une el requestJson del servidor en un GetRestoreCredentialOption dentro de un GetCredentialRequest y llama a getCredential() en el objeto CredentialManager.

Implementa una estrategia de restablecimiento de dos niveles para que los usuarios accedan, independientemente de si se ejecuta o no el restablecimiento de datos de app en segundo plano:

  • Nivel 1 (restablecimiento en segundo plano en BackupAgent.onRestoreFinished()): Si la copia de seguridad de los datos de la app está habilitada (android:allowBackup="true"), crea una subclase de BackupAgent y anula onRestoreFinished() (no uses onRestore(), que solo se llama para las copias de seguridad de pares clave-valor heredadas). Como onRestoreFinished() es síncrona y getCredential() es una función suspend de Kotlin, une la llamada con runBlocking(Dispatchers.IO). Se detecta NoCredentialException de forma silenciosa cuando no hay una clave de restablecimiento.
  • Nivel 2 (resguardo en primer plano en el selector Activity.onCreate()): En el primer inicio de la app, cuando no exista una sesión local, llama a getCredential() desde lifecycleScope antes de mostrar la pantalla de acceso. Esto controla las apps con android:allowBackup="false" y se recupera si el dispositivo no tenía conexión cuando se ejecutó onRestoreFinished().

Si agregas un nuevo BackupAgent a una app que ya usa la Copia de seguridad automática con android:allowBackup="true", también establece android:fullBackupOnly="true" en el elemento <application> de AndroidManifest.xml para que Android siga usando la Copia de seguridad automática en lugar de cambiar a la copia de seguridad de par clave-valor heredada.

    suspend fun getRestoreKey(
        fetchAuthenticationJson: () -> String,
    ) {
        // Fetch the options required to get the restore key
        val authenticationJson = fetchAuthenticationJson()

        // Create the GetRestoreCredentialOption object
        val options = GetRestoreCredentialOption(authenticationJson)
        val getRequest = GetCredentialRequest(listOf(options))

        try {
            val response = credentialManager.getCredential(context, getRequest)

            // Type-check and extract the restore credential
            val credential = response.credential
            if (credential is RestoreCredential) {
                val responseJson = credential.authenticationResponseJson
            }
        } catch (e: NoCredentialException) {
            // No restore key is present on this device
        }
    }

class MyBackupAgent : BackupAgent() {

    override fun onRestoreFinished() {
        super.onRestoreFinished()
        val credentialManager = CredentialManager.create(this)
        val restoreFunctions = RestoreCredentialsFunctions(this, credentialManager)

        runBlocking(Dispatchers.IO) {
            restoreFunctions.getRestoreKey(
                fetchAuthenticationJson = {
                    /* Fetch challenge JSON from server */ ""
                }
            )
        }
    }
}

La API de Credential Manager devuelve un GetCredentialResponse cuya propiedad credential es un RestoreCredential (no un PublicKeyCredential) que contiene authenticationResponseJson.

Controla la respuesta de acceso

Envía restoreCredential.authenticationResponseJson desde la app al servidor de la parte que confía para verificar la aserción criptográfica y emitir un nuevo token de sesión para el usuario. Para obtener detalles sobre la verificación del servidor, consulta Accede con una llave de acceso.

Cuando diseñes el almacenamiento de backend y las políticas de ciclo de vida para las claves de restablecimiento, sigue estos lineamientos:

  • Diferencia las claves de restablecimiento de las llaves de acceso de los usuarios: Etiqueta las claves de restablecimiento en tu base de datos para que se oculten de las pantallas de administración de llaves de acceso para el usuario y estén exentas de las verificaciones de usuario (uv = true).
  • Admite varios dispositivos activos por usuario: Un usuario puede tener varios dispositivos (como un teléfono y una tablet). Permite varias claves de restablecimiento activas por cuenta de usuario (por ejemplo, con clave por ID de dispositivo o de instalación) en lugar de reemplazar una sola fila.
  • Establece un TTL adecuado y limpia las claves huérfanas: Cuando se desinstala una app o se borran los datos de app, se borra la clave de restablecimiento local sin notificar al servidor. Conserva las claves de restablecimiento durante el tiempo suficiente para que sobrevivan a los períodos de actualización del dispositivo (incluso si el usuario sale de su cuenta en el dispositivo anterior después de la transferencia) y descarta las claves inactivas según el TTL de inactividad o los límites de claves por usuario.

Borra la clave de restablecimiento

Credential Manager no tiene estado y no conoce la actividad del usuario, por lo que no borra automáticamente las claves de restablecimiento después de su uso. Para borrar una clave de restablecimiento, llama al método clearCredentialState(). Por motivos de seguridad, borra la clave cada vez que un usuario salga de su cuenta para que no vuelva a acceder de forma silenciosa la próxima vez que se inicie la app.

Las claves de restablecimiento solo se quitan en las siguientes situaciones:

  • Acciones a nivel del sistema: Los usuarios desinstalan la app o borran sus datos.
  • Llamadas a nivel de la app: Borra la clave de forma programática llamando a clearCredentialState() cuando se controle la salida del usuario en el código de tu app.

Debido a que un ClearCredentialStateRequest() predeterminado solo borra el estado de la sesión estándar y deja intacta la clave de restablecimiento, realiza un borrado doble cuando se salga de la sesión:

  1. Llama a credentialManager.clearCredentialState(ClearCredentialStateRequest()) para borrar el estado de la sesión activa del proveedor de credenciales.
  2. Llama a credentialManager.clearCredentialState() con ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) para borrar el RestoreCredential almacenado.
  3. Borra el marcador has_synced_restore_credential local del almacenamiento local.
// Clear active credential provider session state
credentialManager.clearCredentialState(ClearCredentialStateRequest())

// When the user logs out, delete the restore key
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
val response = credentialManager.clearCredentialState(clearRequest)

Próximos pasos