Cómo crear una llave de acceso

Antes de que tus usuarios puedan autenticarse con llaves de acceso, tu app debe registrar o crear la llave de acceso para su cuenta.

Para crear la llave de acceso, obtén los detalles necesarios del servidor de tu app y, luego, llama a la API de Credential Manager, que devuelve un par de claves pública y privada. La clave privada que se devuelve se almacena en un proveedor de credenciales, como el Administrador de contraseñas de Google, como una llave de acceso. La clave pública se almacena en el servidor de tu app.

Las llaves de acceso se almacenan en un proveedor de credenciales y las claves públicas se almacenan en el servidor de la app.
Figura 1: Creación de llaves de acceso

Requisitos previos

Asegúrate de haber configurado Digital Asset Links y de que tu app se oriente a dispositivos que ejecuten Android 9 (nivel de API 28) o versiones posteriores.

Descripción general

En esta guía, se explican los cambios necesarios en tu app cliente de la entidad de confianza para crear una llave de acceso y se proporciona una breve descripción general de la implementación del servidor de la app de la entidad de confianza. Para obtener más información sobre la integración del servidor, consulta Registro de llaves de acceso del servidor.

  1. Agrega dependencias a tu app: Agrega las bibliotecas necesarias del Administrador de credenciales.
  2. Crea una instancia de Credential Manager: Crea una instancia de Credential Manager.
  3. Obtén opciones de creación de credenciales del servidor de la app: Desde el servidor de tu app, envía a la app cliente los detalles necesarios para crear la llave de acceso, como información sobre la app, el usuario, un challenge y otros campos.
  4. Solicita una llave de acceso: En tu app, usa los detalles que recibiste del servidor de la app para crear un objeto GetPublicKeyCredentialOption y, luego, invoca el método credentialManager.getCredential() para crear una llave de acceso.
  5. Controla la respuesta de creación de la llave de acceso: Cuando recibas las credenciales en tu app cliente, debes codificar, serializar y, luego, enviar la clave pública al servidor de la app. También debes controlar cada una de las excepciones que pueden ocurrir en caso de creación de una llave de acceso.
  6. Verifica y guarda la clave pública en el servidor: Completa los pasos del servidor para verificar el origen de la credencial y, luego, guarda la clave pública.
  7. Notificar al usuario: Notifica al usuario que se creó su llave de acceso.

1. Agrega dependencias a tu app

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

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.6.0-beta03")
    implementation("androidx.credentials:credentials-play-services-auth:1.6.0-beta03")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.6.0-beta03"
    implementation "androidx.credentials:credentials-play-services-auth:1.6.0-beta03"
}

2. Crea una instancia del Administrador de credenciales

Usa el contexto de tu app o actividad para crear un objeto CredentialManager.

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

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

Cuando el usuario hace clic en el botón "Crear llave de acceso" o cuando un usuario nuevo se registra, haz una solicitud desde tu app a tu servidor de apps para obtener la información necesaria para iniciar el proceso de registro de la llave de acceso.

Usa una biblioteca compatible con FIDO en el servidor de tu app para enviar a la app cliente la información necesaria para crear una llave de acceso, como información sobre el usuario, la app y propiedades de configuración adicionales. Para obtener más información, consulta Registro de llaves de acceso del servidor.

En la app cliente, decodifica las opciones de creación de claves públicas que envió el servidor de la app. Por lo general, se representan en formato JSON. Para obtener más información sobre cómo se realiza esta decodificación para los clientes web, consulta Codificación y decodificación. En el caso de las apps cliente para Android, debes controlar la decodificación por separado.

En el siguiente fragmento, se muestra la estructura de las opciones de creación de claves públicas que envía el servidor de la app:

{
  "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"
  }
}

Entre los campos clave de las opciones de creación de claves públicas, se incluyen los siguientes:

  • challenge: Es una cadena aleatoria generada por el servidor que se usa para evitar ataques de reproducción.
  • rp: Detalles sobre la app.
    • rp.name: Es el nombre de la app.
    • rp.id: Es el dominio o subdominio de la app.
  • user: Son los detalles sobre el usuario.
    • id: Es el ID único del usuario. Este valor no debe incluir información de identificación personal, por ejemplo, direcciones de correo electrónico o nombres de usuario. Puedes usar un valor aleatorio de 16 bytes.
    • name: Es un identificador único para la cuenta que el usuario reconocerá, como su dirección de correo electrónico o nombre de usuario. Se mostrará en el selector de cuentas. Si utilizas un nombre de usuario, usa el mismo valor que en la autenticación con contraseña.
    • displayName: Es un nombre opcional y fácil de usar para la cuenta, que se mostrará en el selector de cuentas.
  • authenticatorSelection: Son los detalles sobre el dispositivo que se usará para la autenticación.

    • authenticatorAttachment: Indica el autenticador preferido. Los valores posibles son los siguientes: - platform: Este valor se usa para un autenticador integrado en el dispositivo del usuario, como un sensor de huellas dactilares. - cross-platform: Este valor se usa para dispositivos en itinerancia, como las llaves de seguridad. Por lo general, no se usa en el contexto de las llaves de acceso. - Sin especificar (recomendado): Si no se especifica este valor, los usuarios tienen la flexibilidad de crear llaves de acceso en sus dispositivos preferidos. En la mayoría de los casos, dejar el parámetro sin especificar es la mejor opción.
      • requireResidentKey: Para crear una llave de acceso, configura el valor de este campo Boolean como true.
      • residentKey: Para crear una llave de acceso, establece el valor en required.
      • userVerification: Se usa para especificar los requisitos para la verificación del usuario durante el registro de una llave de acceso. Los valores posibles son los siguientes: - preferred: Usa este valor si priorizas la experiencia del usuario por sobre la protección, como en entornos en los que la verificación del usuario causa más fricción que protección. - required: Usa este valor si se requiere invocar un método de verificación del usuario disponible en el dispositivo. - discouraged: Usa este valor si no se recomienda usar un método de verificación del usuario.
        Para obtener más información sobre userVerification, consulta la profundización de userVerification.
  • excludeCredentials: Enumera los IDs de credenciales en un array para evitar que se cree una llave de acceso duplicada si ya existe una con el mismo proveedor de credenciales.

4. Solicita una llave de acceso

Después de analizar las opciones de creación de claves públicas del servidor, crea una llave de acceso envolviendo estas opciones en un objeto CreatePublicKeyCredentialRequest y llamando a createCredential().

createPublicKeyCredentialRequest incluye lo siguiente:

  • requestJson: Son las opciones de creación de credenciales que envía el servidor de apps.
  • preferImmediatelyAvailableCredentials: Es un campo booleano opcional que define si se deben usar solo las credenciales disponibles localmente o las credenciales sincronizadas con el proveedor de credenciales para completar la solicitud, en lugar de las credenciales de las llaves de seguridad o los flujos de claves híbridos. Los posibles usos son los siguientes:
    • false (predeterminado): Usa este valor si una acción explícita del usuario activó la llamada al Administrador de credenciales.
    • true: Usa este valor si se llama a Credential Manager de forma oportunista, por ejemplo, cuando se abre la app por primera vez.
      Si configuras el valor como true y no hay credenciales disponibles de inmediato, Credential Manager no mostrará ninguna IU y la solicitud fallará de inmediato, y devolverá NoCredentialException para solicitudes GET y CreateCredentialNoCreateOptionException para solicitudes de creación.
  • origin: Este campo se establece automáticamente para las apps para Android. En el caso de los navegadores y las apps con privilegios similares que necesitan establecer origin, consulta Cómo realizar llamadas con Credential Manager en nombre de terceros para apps con privilegios.
  • isConditional: Este es un campo opcional que tiene el valor predeterminado false. Cuando configuras este parámetro en true y si un usuario no tiene una llave de acceso, creas una automáticamente en su nombre la próxima vez que acceda con una contraseña guardada. La llave de acceso se almacena en el proveedor de credenciales del usuario. La función de creación condicional requiere la versión más reciente de androidx.credentials.

Llamar a la función createCredential() inicia la IU integrada de la hoja inferior de Credential Manager, que le solicita al usuario que use una llave de acceso y que seleccione un proveedor de credenciales y una cuenta para el almacenamiento. Sin embargo, si isConditional se establece en true, no se muestra la IU de la hoja inferior y se crea automáticamente la llave de acceso.

5. Cómo controlar la respuesta

Después de que se verifica al usuario con el bloqueo de pantalla del dispositivo, se crea una llave de acceso y se almacena en el proveedor de credenciales seleccionado por el usuario.

La respuesta después de que llamas a createCredential() correctamente es un objeto PublicKeyCredential.

El PublicKeyCredential se ve de la siguiente manera:

{
  "id": "<identifier>",
  "type": "public-key",
  "rawId": "<identifier>",
  "response": {
    "clientDataJSON": "<ArrayBuffer encoded object with the origin and signed challenge>",
    "attestationObject": "<ArrayBuffer encoded object with the public key and other information.>"
  },
  "authenticatorAttachment": "platform"
}

En la app cliente, serializa el objeto y envíalo al servidor de apps.

Agrega código para controlar las fallas, como se muestra en el siguiente fragmento:

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
        }
        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 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}")
    }
}

6. Verifica y guarda la clave pública en el servidor de la app

En el servidor de la app, debes verificar la credencial de clave pública y, luego, guardar la clave pública.

Para verificar el origen de la credencial de clave pública, compárala con una lista de entidades permitidas de apps aprobadas. Si una clave tiene un origen no reconocido, recházala.

Para obtener la huella digital SHA-256 de la app, haz lo siguiente:

  1. Ejecuta el siguiente comando en una terminal para imprimir el certificado de firma de la app de versión:

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

    En la respuesta, identifica la huella digital SHA-256 del certificado de firma, que se menciona como Certificate fingerprints block : SHA256.

  2. Codifica la huella digital SHA256 con la codificación base64url. En este ejemplo de Python, se muestra cómo codificar correctamente la huella digital:

    import binascii
    import base64
    fingerprint = '<SHA256 finerprint>' # your app's SHA256 fingerprint
    print(base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
    
  3. Agrega android:apk-key-hash: al comienzo del resultado del paso anterior para obtener algo similar a lo siguiente:

    android:apk-key-hash:<encoded SHA 256 fingerprint>
    

    El resultado debe coincidir con un origen permitido en el servidor de tu app. Si tienes varios certificados de firma (por ejemplo, certificados para depuración y lanzamiento) o varias apps, repite el proceso y acepta todos los orígenes como válidos en el servidor de la app.

7. Notifica al usuario

Después de que se cree la llave de acceso correctamente, notifica a los usuarios sobre ella y diles que pueden administrar sus llaves de acceso desde la app del proveedor de credenciales o desde la configuración de la app. Notifica a los usuarios con un diálogo, una notificación o una barra de notificaciones personalizados. Dado que la creación inesperada de una llave de acceso por parte de una entidad maliciosa requiere una alerta de seguridad inmediata, considera complementar estos métodos integrados en la app con comunicación externa, como un correo electrónico.

Cómo mejorar la experiencia del usuario

Para mejorar la experiencia del usuario cuando implementes el registro con Credential Manager, considera agregar funciones para restablecer credenciales y suprimir los diálogos de autocompletado.

Agrega la función para restablecer credenciales en un dispositivo nuevo

Para permitir que los usuarios accedan a sus cuentas sin problemas en un dispositivo nuevo, implementa la función Restaurar credenciales. Si agregas credenciales de restablecimiento con BackupAgent, los usuarios accederán a tu app restablecida cuando la abran en un dispositivo nuevo, lo que les permitirá usarla de inmediato.

Cómo suprimir el autocompletado en los campos de credenciales (opcional)

Para las pantallas de la app en las que se espera que los usuarios utilicen la IU de la hoja inferior de Credential Manager para la autenticación, agrega el atributo isCredential a los campos de nombre de usuario y contraseña. Esto evita que los diálogos de autocompletado (FillDialog y SaveDialog) se superpongan con la IU de la hoja inferior de Credential Manager.

El atributo isCredential es compatible con Android 14 y versiones posteriores.

En el siguiente ejemplo, se muestra cómo puedes agregar el atributo isCredential a los campos de nombre de usuario y contraseña correspondientes en las vistas pertinentes de tu app:

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

Próximos pasos