Accede con una llave de acceso mediante el autocompletado de formularios

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

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 una autenticación basada en contraseñas a una basada en llaves de acceso puede complicar la experiencia del usuario. Usar el 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, un usuario puede acceder a un sitio web solo con 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 solo botón de acceso. Cuando el usuario presiona el botón, aparece un diálogo de selección de cuenta, que puede elegir una cuenta, desbloquear la pantalla para verificarla y acceder.

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

Las llaves de acceso también son una nueva tecnología. Explicarlas y asegurarse de que los usuarios se sientan cómodos usándolas 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

A fin de 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 de WebAuthn.

En cuanto el usuario presiona el campo de entrada del nombre de usuario, aparece un diálogo de sugerencia de Autocompletar que destaca las llaves de acceso almacenadas junto con 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 uno).

Cómo funciona

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 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: El frontend que se comunica con el navegador y envía solicitudes de recuperación al backend
  • Navegador: Es el navegador del usuario que ejecuta 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 se usa Windows Hello) o en otro dispositivo, como un teléfono.
Diagrama de autenticación con llave de acceso
  1. Tan pronto como un usuario llega al frontend, este solicita un desafío del backend para autenticarse con una llave de acceso y llama a navigator.credentials.get() para iniciar la autenticación con una llave de acceso. Se 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 con 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 mediante 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 que coincide en la base de datos. Si se ejecuta 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.

Autentica con una llave de acceso mediante el autocompletado de formularios

Cuando un usuario quiera acceder, puedes realizar una llamada condicional a get de WebAuthn para indicar que se pueden incluir llaves de acceso 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. Es responsabilidad de la página permitir que el usuario acceda.

Anotar el campo de entrada del formulario

Si es necesario, agrega un atributo autocomplete al campo de nombre de usuario input. 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 condicional a la API de WebAuthn, 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 de RP

Recupera un desafío del servidor de RP que se requiera 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 desafío nuevo en cada intento de acceso e 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 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 muestre 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. Configura esto 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 del 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.

Enviar la credencial de clave pública que se muestra al servidor de RP

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

Una promesa se puede rechazar por diferentes motivos. 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 un diálogo de error al usuario.

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: Es una versión de 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 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: Es un ArrayBuffer que incluye el ID de 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 crear un índice con los IDs de credenciales.
  • authenticatorAttachment: Muestra platform cuando esta credencial provino del dispositivo local. De lo contrario, cross-platform, en particular cuando el usuario usó un teléfono para acceder. Si el usuario necesita usar un teléfono para acceder, considera pedirle 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 el objeto completo 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 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). Verifica si se puede verificar el signature de la credencial con la clave pública almacenada. Para ello, te recomendamos que uses una biblioteca del servidor o una solución en lugar de escribir tu propio código. Encuentra bibliotecas de código abierto en el repositorio de GitHub increíble de webauth.

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

Recursos