Cómo hacer que el usuario acceda con el Administrador de credenciales

El Administrador de credenciales es una API de Jetpack que admite varios métodos de acceso, como nombres de usuario y contraseñas, llaves de acceso y soluciones de acceso federado (como Acceder con Google) en una sola API, lo que simplifica la integración para los desarrolladores.

Además, para los usuarios, el Administrador de credenciales unifica la interfaz de acceso en todos los métodos de autenticación, lo que permite que los usuarios accedan a las apps de manera más clara y sencilla, independientemente del método que elijan.

En esta página, se explica el concepto de llaves de acceso y los pasos para implementar la compatibilidad del cliente en soluciones de autenticación, incluidas las llaves de acceso, a través de la API de Credential Manager. También hay una página de preguntas frecuentes independiente que brinda respuestas a preguntas más detalladas y específicas.

Tus comentarios son fundamentales para mejorar la API de Credential Manager. Usa el siguiente vínculo para compartir cualquier problema que encuentres o ideas para mejorar la API:

Enviar comentarios

Información acerca de las llaves de acceso

Las llaves de acceso son el reemplazo más seguro y sencillo de las contraseñas. Con las llaves de acceso, los usuarios pueden acceder a apps y sitios web usando un sensor biométrico (como una huella dactilar o el reconocimiento facial), un PIN o un patrón. Esto proporciona una experiencia de acceso fluida, lo que permite que los usuarios no tengan que recordar nombres de usuario o contraseñas.

Las llaves de acceso dependen de WebAuthn (Web Authentication), un estándar desarrollado de forma conjunta por la FIDO Alliance y el World Wide Web Consortium (W3C). WebAuthn usa la criptografía de clave pública para autenticar al usuario. El sitio web o la app a los que accede el usuario pueden ver y almacenar la clave pública, pero nunca la privada. Esta se mantiene en secreto y segura. Debido a que la clave es única y está vinculada al sitio web o la app, las llaves de acceso no son vulnerables a la suplantación de identidad (phishing), lo que aumenta la seguridad.

El Administrador de credenciales permite que los usuarios creen llaves de acceso y las almacenen en el Administrador de contraseñas de Google.

Requisitos previos

Para usar el Administrador de credenciales, completa los pasos que se indican en esta sección.

Usa una versión reciente de la plataforma

Credential Manager es compatible con Android 4.4 (nivel de API 19) y versiones posteriores.

Agrega dependencias a tu app

Agrega las siguientes dependencias a la secuencia de comandos de compilación del módulo de tu app:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.3.0-alpha01")

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.3.0-alpha01")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.3.0-alpha01"

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation "androidx.credentials:credentials-play-services-auth:1.3.0-alpha01"
}

Conserva las clases en un archivo ProGuard

En el archivo proguard-rules.pro de tu módulo, agrega las siguientes directivas:

-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
  *;
}

Obtén más información para reducir, ofuscar y optimizar tu app.

Agrega compatibilidad con Vínculos de recursos digitales

Para habilitar la compatibilidad con las llaves de acceso en tu app para Android, asóciala con un sitio web que le pertenezca a la app. Para declarar esta asociación, completa los siguientes pasos:

  1. Crea un archivo JSON de Vínculos de recursos digitales. Por ejemplo, para declarar que el sitio web https://signin.example.com y una app para Android con el nombre de paquete com.example pueden compartir credenciales de acceso, crea un archivo llamado assetlinks.json con el siguiente contenido:

    [
      {
        "relation" : [
          "delegate_permission/common.handle_all_urls",
          "delegate_permission/common.get_login_creds"
        ],
        "target" : {
          "namespace" : "android_app",
          "package_name" : "com.example.android",
          "sha256_cert_fingerprints" : [
            SHA_HEX_VALUE
          ]
        }
      }
    ]
    

    El campo relation es un array de una o más cadenas que describen la relación que se declara. Para declarar que las apps y los sitios comparten credenciales de acceso, especifica las relaciones delegate_permission/handle_all_urls y delegate_permission/common.get_login_creds.

    El campo target es un objeto que especifica el recurso al que se aplica la declaración. Los siguientes campos identifican un sitio web:

    namespace web
    site

    La URL del sitio web, en el formato https://domain[:optional_port]; por ejemplo, https://www.example.com.

    domain debe estar completamente calificado y se debe omitir optional_port cuando se usa el puerto 443 para HTTPS.

    Un destino site solo puede ser un dominio raíz: no puedes limitar una asociación de app a un subdirectorio específico. No incluyas una ruta en la URL, como una barra final.

    No se considera que los subdominios coincidan, es decir, si especificas domain como www.example.com, el dominio www.counter.example.com no estará asociado a tu app.

    En los siguientes campos, se identifica una app para Android:

    namespace android_app
    package_name El nombre de paquete declarado en el manifiesto de la app. Por ejemplo: com.example.android.
    sha256_cert_fingerprints Las huellas digitales SHA256 del certificado de firma de tu app.
  2. Aloja el archivo JSON de Vínculos de recursos digitales en la siguiente ubicación del dominio de acceso:

    https://domain[:optional_port]/.well-known/assetlinks.json
    

    Por ejemplo, si el dominio de acceso es signin.example.com, aloja el archivo JSON en https://signin.example.com/.well-known/assetlinks.json.

    El tipo de MIME del archivo de Vínculos de recursos digitales debe ser JSON. Asegúrate de que el servidor envíe un encabezado Content-Type: application/json en la respuesta.

  3. Asegúrate de que tu host permita que Google recupere tu archivo de Vínculos de recursos digitales. Si tienes un archivo robots.txt, este debe permitir que el agente de Googlebot recupere /.well-known/assetlinks.json. La mayoría de los sitios pueden permitir que cualquier agente automatizado recupere archivos en la ruta de acceso /.well-known/ para que otros servicios puedan acceder a los metadatos en esos archivos:

    User-agent: *
    Allow: /.well-known/
    
  4. Agrega la siguiente línea al archivo de manifiesto en <application>:

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
    
  5. Si usas el acceso con contraseña a través de Credential Manager, sigue este paso para configurar la vinculación de recursos digitales en el manifiesto (este paso no es obligatorio si solo usas llaves de acceso).

    Declara la asociación en la app para Android. Agrega un objeto que especifique los archivos assetlinks.json que se cargarán. Debes escapar cualquier apóstrofo y comillas que uses en la cadena. Por ejemplo:

    <string name="asset_statements" translatable="false">
    [{
      \"include\": \"https://signin.example.com/.well-known/assetlinks.json\"
    }]
    </string>
    
    > GET /.well-known/assetlinks.json HTTP/1.1
    > User-Agent: curl/7.35.0
    > Host: signin.example.com
    
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    

Cómo configurar el Administrador de credenciales

Para configurar e inicializar un objeto CredentialManager, agrega una lógica similar a la siguiente:

Kotlin

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

Java

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

Cómo indicar campos de credenciales

En Android 14 y versiones posteriores, se puede usar el atributo isCredential para indicar campos de credenciales, como campos de nombre de usuario o contraseña. Este atributo indica que esta vista es un campo de credenciales diseñado para funcionar con Credential Manager y proveedores de credenciales de terceros y, al mismo tiempo, ayudar a los servicios de autocompletado a proporcionar mejores sugerencias de autocompletado. Cuando la app usa la API de Credential Manager, se muestra la hoja inferior del Administrador de credenciales con las credenciales disponibles, y no es necesario mostrar el diálogo de relleno de autocompletado para el nombre de usuario o la contraseña.

Para usar el atributo isCredential, agrégalo a las vistas relevantes:

<TextView
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:isCredential="true"
...
 />

Cómo dar acceso a tu usuario

Para recuperar todas las opciones de llaves de acceso y contraseñas asociadas con la cuenta del usuario, completa estos pasos:

  1. Inicializa las opciones de autenticación con contraseñas y llaves de acceso:

    Kotlin

    // Retrieves the user's saved password for your app from their
    // password provider.
    val getPasswordOption = GetPasswordOption()
    
    // Get passkey from the user's public key credential provider.
    val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
        requestJson = requestJson
    )

    Java

    // Retrieves the user's saved password for your app from their
    // password provider.
    GetPasswordOption getPasswordOption = new GetPasswordOption();
    
    // Get passkey from the user's public key credential provider.
    GetPublicKeyCredentialOption getPublicKeyCredentialOption =
            new GetPublicKeyCredentialOption(requestJson);
  2. Usa las opciones recuperadas del paso anterior para compilar la solicitud de acceso.

    Kotlin

    val getCredRequest = GetCredentialRequest(
        listOf(getPasswordOption, getPublicKeyCredentialOption)
    )

    Java

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. Inicia el flujo de acceso:

    Kotlin

    coroutineScope.launch {
        try {
            val result = credentialManager.getCredential(
                // Use an activity-based context to avoid undefined system UI
                // launching behavior.
                context = activityContext,
                request = getCredRequest
            )
            handleSignIn(result)
        } catch (e : GetCredentialException) {
            handleFailure(e)
        }
    }
    
    fun handleSignIn(result: GetCredentialResponse) {
        // Handle the successfully returned credential.
        val credential = result.credential
    
        when (credential) {
            is PublicKeyCredential -> {
                val responseJson = credential.authenticationResponseJson
                // Share responseJson i.e. a GetCredentialResponse on your server to
                // validate and  authenticate
            }
            is PasswordCredential -> {
                val username = credential.id
                val password = credential.password
                // Use id and password to send to your server to validate
                // and authenticate
            }
          is CustomCredential -> {
              // If you are also using any external sign-in libraries, parse them
              // here with the utility functions provided.
              if (credential.type == ExampleCustomCredential.TYPE)  {
              try {
                  val ExampleCustomCredential = ExampleCustomCredential.createFrom(credential.data)
                  // Extract the required credentials and complete the authentication as per
                  // the federated sign in or any external sign in library flow
                  } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
                      // Unlikely to happen. If it does, you likely need to update the dependency
                      // version of your external sign-in library.
                      Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
                  }
              } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential")
              }
            } else -> {
                // Catch any unrecognized credential type here.
                Log.e(TAG, "Unexpected type of credential")
            }
        }
    }

    Java

    credentialManager.getCredentialAsync(
        // Use activity based context to avoid undefined
        // system UI launching behavior
        activity,
        getCredRequest,
        cancellationSignal,
        <executor>,
        new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
            @Override
            public void onResult(GetCredentialResponse result) {
                handleSignIn(result);
            }
    
            @Override
            public void onError(GetCredentialException e) {
                handleFailure(e);
            }
        }
    );
    
    public void handleSignIn(GetCredentialResponse result) {
        // Handle the successfully returned credential.
        Credential credential = result.getCredential();
        if (credential instanceof PublicKeyCredential) {
            String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson();
            // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate
        } else if (credential instanceof PasswordCredential) {
            String username = ((PasswordCredential) credential).getId();
            String password = ((PasswordCredential) credential).getPassword();
            // Use id and password to send to your server to validate and authenticate
        } else if (credential instanceof CustomCredential) {
            if (ExampleCustomCredential.TYPE.equals(credential.getType())) {
                try {
                    ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData());
                    // Extract the required credentials and complete the
                    // authentication as per the federated sign in or any external
                    // sign in library flow
                } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) {
                    // Unlikely to happen. If it does, you likely need to update the
                    // dependency version of your external sign-in library.
                    Log.e(TAG, "Failed to parse an ExampleCustomCredential", e);
                }
            } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential");
            }
        } else {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential");
        }
    }

En el siguiente ejemplo, se muestra cómo dar formato a la solicitud JSON cuando obtienes una llave de acceso:

{
  "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
  "allowCredentials": [],
  "timeout": 1800000,
  "userVerification": "required",
  "rpId": "credential-manager-app-test.glitch.me"
}

En el siguiente ejemplo, se muestra cómo podría verse una respuesta JSON después de que obtienes una credencial de clave pública:

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
    "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
    "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
  }
}

Controla excepciones cuando no hay credenciales disponibles

En algunos casos, es posible que el usuario no tenga ninguna credencial disponible o que no otorgue su consentimiento para usar una credencial disponible. Si se invoca a getCredential() y no se encuentran credenciales, se muestra una NoCredentialException. Si esto sucede, tu código debería controlar las instancias de NoCredentialException.

Kotlin

try {
  val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
  Log.e("CredentialManager", "No credential available", e)
}

Java

try {
  Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
  Log.e("CredentialManager", "No credential available", e);
}

En Android 14 o versiones posteriores, puedes reducir la latencia cuando se muestra el selector de cuentas con el método prepareGetCredential() antes de llamar a getCredential().

Kotlin

val response = credentialManager.prepareGetCredential(
  GetCredentialRequest(
    listOf(
      <getPublicKeyCredentialOption>,
      <getPasswordOption>
    )
  )
}

Java

GetCredentialResponse response = credentialManager.prepareGetCredential(
  new GetCredentialRequest(
    Arrays.asList(
      new PublicKeyCredentialOption(),
      new PasswordOption()
    )
  )
);

El método prepareGetCredential() no invoca elementos de la IU. Solo te ayuda a realizar el trabajo de preparación para que luego puedas iniciar la operación de obtención de credenciales restante (que incluye IUs) a través de la API de getCredential().

Los datos almacenados en caché se muestran en un objeto PrepareGetCredentialResponse. Si hay credenciales existentes, los resultados se almacenarán en caché y, luego, podrás iniciar la API de getCredential() restante para abrir el selector de cuentas con los datos almacenados en caché.

Flujos de registro

Puedes registrar un usuario para la autenticación usando una llave de acceso o una contraseña.

Cómo crear una llave de acceso

Para darles a los usuarios la opción de inscribir una llave de acceso y usarla para la reautenticación, registra una credencial de usuario usando un objeto CreatePublicKeyCredentialRequest.

Kotlin

fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) {
    val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
        // Contains the request in JSON format. Uses the standard WebAuthn
        // web JSON spec.
        requestJson = requestJson,
        // Defines whether you prefer to use only immediately available
        // credentials, not hybrid credentials, to fulfill this request.
        // This value is false by default.
        preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
    )

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    coroutineScope.launch {
        try {
            val result = credentialManager.createCredential(
                // Use an activity-based context to avoid undefined system
                // UI launching behavior
                context = activityContext,
                request = createPublicKeyCredentialRequest,
            )
            handlePasskeyRegistrationResult(result)
        } catch (e : CreateCredentialException){
            handleFailure(e)
        }
    }
}

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
            handlePasskeyError(e.domError)
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialUnknownException -> ...
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

Java

public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) {
    CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
            // `requestJson` contains the request in JSON format. Uses the standard
            // WebAuthn web JSON spec.
            // `preferImmediatelyAvailableCredentials` defines whether you prefer
            // to only use immediately available credentials, not  hybrid credentials,
            // to fulfill this request. This value is false by default.
            new CreatePublicKeyCredentialRequest(
                requestJson, preferImmediatelyAvailableCredentials);

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined system
        // UI launching behavior
        requireActivity(),
        createPublicKeyCredentialRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleSuccessfulCreatePasskeyResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                if (e instanceof CreatePublicKeyCredentialDomException) {
                    // Handle the passkey DOM errors thrown according to the
                    // WebAuthn spec.
                    handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError());
                } else if (e instanceof CreateCredentialCancellationException) {
                    // The user intentionally canceled the operation and chose not
                    // to register the credential.
                } else if (e instanceof CreateCredentialInterruptedException) {
                    // Retry-able error. Consider retrying the call.
                } else if (e instanceof CreateCredentialProviderConfigurationException) {
                    // Your app is missing the provider configuration dependency.
                    // Most likely, you're missing the
                    // "credentials-play-services-auth" module.
                } else if (e instanceof CreateCredentialUnknownException) {
                } else if (e instanceof CreateCredentialCustomException) {
                    // You have encountered an error from a 3rd-party SDK. If
                    // you make the API call with a request object that's a
                    // subclass of
                    // CreateCustomCredentialRequest using a 3rd-party SDK,
                    // then you should check for any custom exception type
                    // constants within that SDK to match with e.type.
                    // Otherwise, drop or log the exception.
                } else {
                  Log.w(TAG, "Unexpected exception type "
                          + e.getClass().getName());
                }
            }
        }
    );
}

Agrega formato a la solicitud JSON

Después de crear una llave de acceso, debes asociarla con la cuenta de un usuario y almacenar la clave pública de la llave de acceso en tu servidor. En el siguiente ejemplo de código, se muestra cómo dar formato a la solicitud JSON cuando creas una llave de acceso.

En esta entrada de blog sobre el modo para llevar la autenticación sin problemas a tus apps, se muestra cómo dar formato a tu solicitud JSON cuando creas llaves de acceso y te autenticas con estas. También se explica por qué las contraseñas no son una solución de autenticación eficaz, cómo aprovechar las credenciales biométricas existentes, cómo asociar tu app a un sitio web de tu propiedad, cómo crear llaves de acceso y cómo realizar la autenticación con llaves de acceso.

{
  "challenge": "abc123",
  "rp": {
    "name": "Credential Manager example",
    "id": "credential-manager-test.example.com"
  },
  "user": {
    "id": "def456",
    "name": "helloandroid@gmail.com",
    "displayName": "helloandroid@gmail.com"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {"id": "ghi789", "type": "public-key"},
    {"id": "jkl012", "type": "public-key"}
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Establece valores para autenticadorAttachment

El parámetro authenticatorAttachment solo se puede configurar durante la creación de la credencial. Puedes especificar platform, cross-platform o ningún valor. En la mayoría de los casos, no se recomienda ningún valor.

  • platform: Para registrar el dispositivo actual del usuario o pedirle a un usuario de contraseña que actualice a las llaves de acceso después de acceder, configura authenticatorAttachment como platform.
  • cross-platform: Este valor se usa comúnmente cuando se inscriben credenciales de varios factores y no se usa en un contexto de llaves de acceso.
  • Sin valor: Para proporcionar a los usuarios la flexibilidad de crear llaves de acceso en sus dispositivos preferidos (como en la configuración de la cuenta), no se debe especificar el parámetro authenticatorAttachment cuando el usuario elige agregar una llave de acceso. En la mayoría de los casos, dejar el parámetro sin especificar es la mejor opción.

Evita la creación de llaves de acceso duplicadas

Enumera los IDs de credenciales en el array opcional excludeCredentials para evitar que se cree una llave de acceso nueva si ya existe una con el mismo proveedor de llaves de acceso.

Controla la respuesta JSON

En el siguiente fragmento de código, se muestra un ejemplo de respuesta JSON para crear una credencial de clave pública. Obtén más información para procesar la credencial de clave pública que se muestra.

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
  }
}

Verifica el origen a partir del JSON de datos del cliente

El objeto origin representa la aplicación o el sitio web de donde proviene una solicitud. Las llaves de acceso usan este código para protegerte de los ataques de phishing. El servidor de tu app debe verificar el origen de los datos del cliente con una lista de apps y sitios web permitidos. Si el servidor recibe una solicitud de una app o un sitio web de un origen no reconocido, se debe rechazar la solicitud.

En el caso web, origin refleja el origen del mismo sitio en el que se accedió con la credencial. Por ejemplo, con una URL https://www.example.com:8443/store?category=shoes#athletic, el origin es https://www.example.com:8443.

En el caso de las apps para Android, el usuario-agente configura automáticamente origin en la firma de la app que realiza la llamada. Esta firma se debe verificar como coincidencia en tu servidor para validar al emisor de la API de la llave de acceso. El origin de Android es un URI derivado del hash SHA-256 del certificado de firma de APK, por ejemplo:

android:apk-key-hash:<sha256_hash-of-apk-signing-cert>

Para encontrar los valores hash SHA-256 de los certificados de firma de un almacén de claves, ejecuta el siguiente comando de la terminal:

keytool -list -keystore <path-to-apk-signing-keystore>

Los valores hash SHA-256 están en formato hexadecimal delimitado por dos puntos (91:F7:CB:F9:D6:81…) y los valores origin de Android están codificados en base64url. En este ejemplo de Python, se muestra cómo convertir el formato de hash a un formato hexadecimal compatible y separado por dos puntos:

import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))

Reemplaza el valor de fingerprint con tu propio valor. A continuación, se muestra un resultado de ejemplo:

android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU

Luego, puedes hacer coincidir esa cadena como un origen permitido en tu servidor. Si tienes varios certificados de firma (por ejemplo, certificados para depuración y lanzamiento) o varias apps, repite el proceso y acepta todos esos orígenes como válidos en el servidor.

Guarda la contraseña del usuario

Si el usuario proporciona un nombre de usuario y una contraseña para un flujo de autenticación en tu app, puedes registrar una credencial de usuario que pueda usarse para autenticarlo. Para hacerlo, crea un objeto CreatePasswordRequest:

Kotlin

fun registerPassword(username: String, password: String) {
    // Initialize a CreatePasswordRequest object.
    val createPasswordRequest =
            CreatePasswordRequest(id = username, password = password)

    // Create credential and handle result.
    coroutineScope.launch {
        try {
            val result =
                credentialManager.createCredential(
                    // Use an activity based context to avoid undefined
                    // system UI launching behavior.
                    activityContext,
                    createPasswordRequest
                  )
            handleRegisterPasswordResult(result)
        } catch (e: CreateCredentialException) {
            handleFailure(e)
        }
    }
}

Java

void registerPassword(String username, String password) {
    // Initialize a CreatePasswordRequest object.
    CreatePasswordRequest createPasswordRequest =
        new CreatePasswordRequest(username, password);

    // Register the username and password.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined
        // system UI launching behavior
        requireActivity(),
        createPasswordRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                handleFailure(e);
            }
        }
    );
}

Admite la recuperación de credenciales

Si un usuario ya no tiene acceso a un dispositivo en el que almacenó sus credenciales, es posible que deba recuperarlas desde una copia de seguridad en línea segura. Si quieres obtener más información sobre cómo admitir este proceso de recuperación de credenciales, consulta la sección "Recovering access or adding new devices" (Cómo recuperar el acceso o agregar dispositivos nuevos) en la entrada de blog Security of Passkeys in the Google Password Manager (Seguridad de las llaves de acceso del Administrador de contraseñas de Google).

Cómo agregar compatibilidad con herramientas de administración de contraseñas con la URL conocida de extremos de claves de acceso

Para lograr una integración continua y una compatibilidad futura con las herramientas de administración de credenciales y contraseñas, recomendamos agregar compatibilidad con URLs conocidas de extremos de llaves de acceso. Este es un protocolo abierto para que las partes alineadas anuncien formalmente su compatibilidad con llaves de acceso y proporcionen vínculos directos a la inscripción y administración de llaves de acceso.

  1. Para un usuario de confianza ubicado en https://example.com, que tiene un sitio web y apps para iOS y Android, la URL conocida sería https://example.com/.well-known/passkey-endpoints.
  2. Cuando se consulta la URL, la respuesta debe usar el siguiente esquema

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. Para que este vínculo se abra directamente en tu app en lugar de en la Web, usa Android App Links.

  4. Puedes encontrar más detalles en la explicación de la URL conocida de extremos de claves de acceso en GitHub.

Cómo solucionar los errores comunes

En la siguiente tabla, se muestran varios códigos de error frecuentes y sus descripciones, y se proporciona información sobre las causas:

Código de error y descripción Causa
On Begin Sign In Failure: 16: Se bloqueó temporalmente el llamador porque se cancelaron demasiados mensajes de acceso.

Si se produce este período de inactividad de 24 horas durante el desarrollo, puedes restablecerlo borrando el almacenamiento de la app de los Servicios de Google Play.

Como alternativa, para activar o desactivar este período de inactividad en un dispositivo o emulador de prueba, ve a la app de Teléfono e ingresa el siguiente código: *#*#66382723#*#*. La app de Teléfono borra todas las entradas y puede cerrarse, pero no se muestra un mensaje de confirmación.

On Begin Sign In Failure: 8: Error interno desconocido.
  1. El dispositivo no está configurado correctamente con la Cuenta de Google.
  2. La llave de acceso JSON se creó de forma incorrecta.
CreatePublicKeyCredentialDomException: La solicitud entrante no se puede validar. El ID de paquete de la app no está registrado en tu servidor. Valida esto en la integración del servidor.
CreateCredentialUnknownException: Al guardar la contraseña, se encontró una respuesta de error de contraseña de One Tap 16: se omite el guardado de contraseña, ya que es probable que se solicite al usuario la función Autocompletar de Android Este error solo ocurre en Android 13 y versiones anteriores, y solo si Google es el proveedor de Autocompletar. En este caso, los usuarios ven un mensaje de guardado de Autocompletar y la contraseña se guarda en el Administrador de contraseñas de Google. Ten en cuenta que las credenciales guardadas con Autocompletar con Google se comparten de forma bidireccional con la API de Credential Manager. Como resultado, este error se puede ignorar de forma segura.

Recursos adicionales

Para obtener más información sobre la API de Credential Manager y las llaves de acceso, consulta los siguientes recursos: