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, se adapte a los usuarios que usan contraseñas.

Las llaves de acceso reemplazan a 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 el autocompletado de formularios para acceder con una llave de acceso?

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

Idealmente, no habría usuarios de contraseñas y el flujo de autenticación podría ser tan simple como un botón de inicio de sesión único. Cuando el usuario presiona el botón, aparece un diálogo del selector de cuentas. El usuario puede elegir una cuenta, desbloquear la pantalla para verificar y acceder.

Sin embargo, la transición de la autenticación basada en contraseñas a la basada en llaves de acceso puede ser un desafío. A medida que los usuarios cambien a las llaves de acceso, seguirá habiendo quienes usen contraseñas, y los sitios web deberán adaptarse a ambos tipos de usuarios. No se espera que los usuarios recuerden en qué sitios cambiaron a las llaves de acceso, por lo que pedirles que seleccionen qué método usar de antemano sería una UX deficiente.

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

IU condicional

Para crear 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 forma parte del estándar WebAuthn.

En cuanto el usuario presiona el campo de entrada de nombre de usuario, aparece un diálogo de sugerencias de autocompletado que destaca las llaves de acceso almacenadas junto con las sugerencias de autocompletado de 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 funciona

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

Los cuatro componentes de un flujo de autenticación de llaves de acceso son los siguientes: El usuario:

  • Backend: Es el 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 tu frontend que se comunica con el navegador y envía solicitudes de recuperación al backend.
  • Navegador: Es el navegador del usuario que ejecuta tu código 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 de llaves 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 de autocompletado de contraseñas que incluye llaves de acceso. Aparecerá un diálogo de autenticación si el usuario selecciona una llave de acceso.
  3. Después de que el usuario verifica su identidad con el bloqueo de pantalla del dispositivo, se resuelve la promesa y se muestra 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á.

Cómo autenticar con una llave de acceso mediante el autocompletado del formulario

Cuando un usuario quiera acceder, puedes realizar una llamada get condicional de WebAuthn para indicar que las llaves de acceso se pueden incluir en las sugerencias de autocompletado. 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 desde las sugerencias de autocompletado. Si el usuario elige una llave de acceso, el navegador resolverá la promesa con una credencial en lugar de completar el formulario de acceso. Luego, es responsabilidad de la página permitir que el usuario acceda.

Anota 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 atributos

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

  • El navegador admite WebAuthn con PublicKeyCredential.

Navegadores compatibles

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Origen

Navegadores compatibles

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

Origen

// 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  
  }  
}  

Recupera un desafío del servidor de RP

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

  • challenge: Es un desafío generado por el servidor en un ArrayBuffer. Esta acción es necesaria para evitar ataques de reproducción. Asegúrate de generar un desafío nuevo en cada intento de acceso y de ignorarlo después de un período determinado o después de que un intento de acceso no se valide. Considéralo como un token de 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 con 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. Establece esta opción en "preferred" o 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 que la solicitud sea condicional.

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

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

Una promesa se puede rechazar por varios motivos. Debes controlar 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: El ID codificado en base64url de la credencial de llave de acceso autenticada.
  • rawId: Una versión de ArrayBuffer del ID de 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: Un ArrayBuffer de datos de autenticador. Este campo contiene información como el ID del RP.
  • response.signature: Un ArrayBuffer de la firma. Este valor es el núcleo de la credencial y se debe verificar en el servidor.
  • response.userHandle: Un ArrayBuffer que contenía el ID de usuario establecido en el momento de la creación. Este valor se puede usar, 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 credencial.
  • authenticatorAttachment: Muestra platform cuando esta credencial proviene del dispositivo local. De lo contrario, muestra cross-platform, en particular cuando el usuario utiliza un teléfono para acceder. Si el usuario necesitó usar un teléfono para acceder, pídele 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 de RP, te recomendamos que envíes todo el objeto al servidor después de codificarlo parcialmente con base64url.

Verifica la firma

Cuando recibas la credencial de clave pública en el servidor, pásala a la biblioteca de 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 el user.id que especificaste cuando creaste la credencial). Comprueba si se puede verificar el signature de la credencial 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 de awesome-webauth.

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

Sigue las instrucciones más detalladas en Autenticación de llaves de acceso del servidor.

Recursos