Accede con una llave de acceso mediante el autocompletado del formulario

Crea una experiencia de acceso que aproveche las llaves de acceso y, al mismo tiempo, permita a los usuarios de contraseñas existentes.

Las llaves de acceso reemplazan las contraseñas y hacen que las cuentas de usuario en la Web sean más seguras, sencillas y fáciles de usar. Sin embargo, la transición de la autenticación basada en contraseñas a la basada en llaves de acceso puede complicar la experiencia del usuario. El uso del autocompletado de formularios para sugerir llaves de acceso puede ayudar a crear una experiencia unificada.

¿Por qué usar la función Autocompletar de formularios para acceder con una llave de acceso?

Con una llave de acceso, un usuario puede acceder a un sitio web con solo usar la huella dactilar, el rostro o el PIN del dispositivo.

Lo ideal sería que no hubiera usuarios con contraseña y el flujo de autenticación podría ser tan simple como un botón de acceso único. Cuando el usuario presiona el botón, aparece un diálogo de selección de cuenta en el que puede elegir una cuenta y desbloquear la pantalla para verificarla y acceder.

Sin embargo, la transición de la autenticación con contraseñas a la autenticación basada en llaves de acceso puede ser desafiante. A medida que los usuarios cambien a las llaves de acceso, seguirán existiendo quienes usen contraseñas y los sitios web deberán admitir ambos tipos de usuarios. No se debe esperar que los usuarios recuerden los sitios que cambiaron a las llaves de acceso, por lo que pedirles a los usuarios que seleccionen qué método usar por adelantado sería una UX deficiente.

Las llaves de acceso también son una tecnología nueva. Explicarlas y asegurarse de que los usuarios se sientan cómodos al usarlas puede ser un desafío para los sitios web. Podemos utilizar experiencias del usuario conocidas para autocompletar contraseñas a fin de resolver ambos problemas.

IU condicional

Si quieres compilar una experiencia del usuario eficiente para los usuarios de llaves de acceso y contraseñas, puedes incluir llaves de acceso en las sugerencias de autocompletado. Esto se denomina IU condicional y es parte del estándar de WebAuthn.

En cuanto el usuario presiona el campo de entrada del nombre de usuario, aparece un diálogo de sugerencias de autocompletado que destaca las llaves de acceso almacenadas junto con las sugerencias para autocompletar contraseñas. Luego, el usuario puede elegir una cuenta y usar el bloqueo de pantalla del dispositivo para acceder.

De esta manera, los usuarios pueden acceder a tu sitio web con el formulario existente como si nada hubiera cambiado, pero con el beneficio de seguridad adicional de las llaves de acceso, si tienen una.

Cómo funcionan

Para autenticar con una llave de acceso, usa la API de WebAuthn.

Los cuatro componentes de un flujo de autenticación con llave de acceso son: el usuario:

  • Backend: Es tu servidor de backend que contiene la base de datos de cuentas que almacena la clave pública y otros metadatos sobre la llave de acceso.
  • Frontend: Es el frontend que se comunica con el navegador y envía solicitudes de recuperación al backend.
  • Navegador: El navegador del usuario que ejecuta tu JavaScript.
  • Autenticador: Es el autenticador del usuario que crea y almacena la llave de acceso. Puede estar en el mismo dispositivo que el navegador (p.ej., cuando usas Windows Hello) o en otro dispositivo, como un teléfono.
Diagrama de autenticación con llave de acceso
  1. En cuanto un usuario llega al frontend, solicita un desafío al backend para autenticarse con una llave de acceso y llama a navigator.credentials.get() para iniciar la autenticación con una llave de acceso. Esto muestra un Promise.
  2. Cuando el usuario coloca el cursor en el campo de acceso, el navegador muestra un diálogo para autocompletar contraseñas que incluye las llaves de acceso. Si el usuario selecciona una llave de acceso, aparecerá un diálogo de autenticación.
  3. Después de que el usuario verifica su identidad con el bloqueo de pantalla del dispositivo, se resuelve la promesa y se devuelve una credencial de clave pública al frontend.
  4. El frontend envía la credencial de clave pública al backend. El backend verifica la firma con la clave pública de la cuenta coincidente en la base de datos. Si se realiza correctamente, el usuario accederá.

Requisitos previos

La IU condicional de WebAuthn es compatible públicamente con Safari en iOS 16, iPadOS 16 y macOS Ventura. También está disponible en Chrome para Android, macOS y Windows 11 22H2.

Cómo autenticar con una llave de acceso usando el autocompletado de formularios

Cuando un usuario quiere acceder, puedes realizar una llamada condicional get de WebAuthn para indicar que se pueden incluir llaves de acceso en las sugerencias de Autocompletar. Una llamada condicional a la API de navigator.credentials.get() de WebAuthn no muestra la IU y permanece pendiente hasta que el usuario elige una cuenta para acceder con las sugerencias de Autocompletar. Si el usuario elige una llave de acceso, el navegador resolverá la promesa con una credencial en lugar de completar el formulario de acceso. Es responsabilidad de la página permitir el acceso del usuario.

Anotar el campo de entrada del formulario

Si es necesario, agrega un atributo autocomplete al campo input del nombre de usuario. Agrega username y webauthn como sus tokens para permitir que sugiera llaves de acceso.

<input type="text" name="username" autocomplete="username webauthn" ...>

Detección de funciones

Antes de invocar una llamada a la API de WebAuthn condicional, verifica lo siguiente:

  • El navegador es compatible con WebAuthn.
  • El navegador admite la IU condicional de WebAuthn.
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.​​isConditionalMediationAvailable) {  
  // Check if conditional mediation is available.  
  const isCMA = await PublicKeyCredential.​​isConditionalMediationAvailable();  
  if (isCMA) {  
    // Call WebAuthn authentication  
  }  
}  

Cómo recuperar un desafío del servidor RP

Obtén un desafío del servidor RP que se requiere para llamar a navigator.credentials.get():

  • challenge: Es un desafío generado por el servidor en un ArrayBuffer. Esto es necesario para evitar ataques de reproducción. Asegúrate de generar un nuevo desafío en cada intento de acceso y, luego, ignóralo después de un período determinado o después de que un intento de acceso no se valide. Considéralo como un token CSRF.
  • allowCredentials: Es un array de credenciales aceptables para esta autenticación. Pasa un array vacío para permitir que el usuario seleccione una llave de acceso disponible de una lista que muestra el navegador.
  • userVerification: Indica si la verificación del usuario mediante el bloqueo de pantalla del dispositivo es "required", "preferred" o "discouraged". El valor predeterminado es "preferred", lo que significa que el autenticador puede omitir la verificación del usuario. Configúralo como "preferred", o bien omite la propiedad.

Llama a la API de WebAuthn con la marca conditional para autenticar al usuario.

Llama a navigator.credentials.get() para comenzar a esperar la autenticación del usuario.

// To abort a WebAuthn call, instantiate an `AbortController`.
const abortController = new AbortController();

const publicKeyCredentialRequestOptions = {
  // Server generated challenge
  challenge: ****,
  // The same RP ID as used during registration
  rpId: 'example.com',
};

const credential = await navigator.credentials.get({
  publicKey: publicKeyCredentialRequestOptions,
  signal: abortController.signal,
  // Specify 'conditional' to activate conditional UI
  mediation: 'conditional'
});
  • rpId: Un ID de RP es un dominio, y un sitio web puede especificar su dominio o un sufijo registrable. Este valor debe coincidir con el rp.id que se usó cuando se creó la llave de acceso.

Recuerda especificar mediation: 'conditional' para hacer la solicitud condicional.

Envía la credencial de clave pública que se muestra al servidor RP

Después de que el usuario selecciona una cuenta y da su consentimiento para usar el bloqueo de pantalla del dispositivo, la promesa se resuelve y muestra un objeto PublicKeyCredential al frontend del RP.

Una promesa puede rechazarse por varias razones diferentes. Debes manejar los errores según corresponda, según la propiedad name del objeto Error:

  • NotAllowedError: El usuario canceló la operación.
  • Otras excepciones: Se produjo un error inesperado. El navegador le muestra al usuario un diálogo de error.

El objeto de credencial de clave pública contiene las siguientes propiedades:

  • id: Es el ID codificado en base64url de la credencial de llave de acceso autenticada.
  • rawId: Es una versión ArrayBuffer del ID de la credencial.
  • response.clientDataJSON: Un ArrayBuffer de datos del cliente. Este campo contiene información como el desafío y el origen que el servidor de RP deberá verificar.
  • response.authenticatorData: Es un ArrayBuffer de datos del autenticador. Este campo contiene información como el ID del RP.
  • response.signature: Es un ArrayBuffer de la firma. Este valor es el núcleo de la credencial y se debe verificar en el servidor.
  • response.userHandle: Es un ArrayBuffer que contenía el ID del usuario que se estableció en el momento de la creación. Se puede usar este valor en lugar del ID de credencial si el servidor necesita elegir los valores de ID que usa o si el backend desea evitar la creación de un índice en los IDs de credenciales.
  • authenticatorAttachment: Muestra platform cuando esta credencial proviene del dispositivo local. De lo contrario, es cross-platform, en particular cuando el usuario usó un teléfono para acceder. Si el usuario tuvo que usar un teléfono para acceder, se recomienda que le pidas que cree una llave de acceso en el dispositivo local.
  • type: Este campo siempre se establece en "public-key".

Si usas una biblioteca para controlar el objeto de credencial de clave pública en el servidor RP, te recomendamos que envíes el objeto completo al servidor después de codificarlo de forma parcial con base64url.

Verifica la firma

Cuando recibas la credencial de clave pública en el servidor, pásala a la biblioteca FIDO para procesar el objeto.

Busca el ID de credencial coincidente con la propiedad id (si necesitas determinar la cuenta de usuario, usa la propiedad userHandle, que es la user.id que especificaste cuando creaste la credencial). Consulta si el signature de la credencial se puede verificar con la clave pública almacenada. Para ello, te recomendamos que uses una biblioteca o una solución del servidor en lugar de escribir tu propio código. Puedes encontrar bibliotecas de código abierto en el repositorio de GitHub awesome-webauth.

Una vez que se verifique la credencial con una clave pública coincidente, haz que el usuario acceda.

Recursos