Creación condicional de llaves de acceso (promoción posterior al acceso)

En esta guía, se detalla cómo registrar automáticamente y de forma silenciosa una llave de acceso para un usuario inmediatamente después de que accede correctamente con una contraseña, lo que minimiza la fricción y aumenta la adopción de llaves de acceso.

El momento de activación adecuado

La creación automática de llaves de acceso (también conocida como creación condicional o promoción silenciosa posterior al acceso) SOLO DEBE activarse inmediatamente después de un acceso completo y exitoso que haya involucrado una contraseña.

  • No intentes la creación condicional para flujos sin contraseña (p.ej., vínculos mágicos, OTP por SMS o federación de identidades).
  • Si se requiere la autenticación de varios factores, DEBES esperar hasta que todos los factores se hayan completado correctamente antes de iniciar la creación condicional.
  • Asegúrate de que haya una sesión de usuario válida y autenticada activa antes de realizar solicitudes a los extremos de creación.

Pasos de implementación

1. Anular acciones de autocompletado anteriores

Si la página de acceso utiliza la función de autocompletar formularios (IU condicional/Get), se debe anular la llamada de obtención de credenciales activa para evitar conflictos con el navegador.

  • Llama a abortController.abort() en el AbortController adjunto a la solicitud de autocompletado navigator.credentials.get() pendiente antes de llamar a navigator.credentials.create().

2. Detección de funciones

Para determinar si la creación condicional está disponible, verifica conditionalCreate con PublicKeyCredential.getClientCapabilities().

const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalCreate) {
  // Conditional create is available
}

3. Crea una llave de acceso con Conditional Create

  • Pasa mediation: 'conditional' dentro de las opciones de navigator.credentials.create(). Esto le indica al navegador que controle el flujo de creación de llaves de acceso de forma silenciosa en segundo plano o de forma contextual sin mostrar diálogos modales invasivos.
  • Propaga excludeCredentials con los IDs de credenciales de llave de acceso existentes del usuario para evitar que se registren claves duplicadas.

4. Manejo de errores silencioso

  • Une el mensaje de creación de la llave de acceso (navigator.credentials.create) en un bloque try/catch. DEBES detectar y, luego, ignorar de forma silenciosa las excepciones típicas para el usuario (InvalidStateError, NotAllowedError, AbortError) sin renderizar ninguna IU de error para el usuario.

5. Verificación de la presencia del usuario del servidor

  • El extremo de verificación del servidor DEBE flexibilizar el requisito de presencia del usuario (UP) (requireUserPresence: false) SOLO cuando se verifiquen las credenciales producidas por un activador de creación condicional. La verificación de presencia estricta debe permanecer activa para las creaciones explícitas estándar.

6. Controla correctamente los errores de verificación del servidor

  • Si navigator.credentials.create() se ejecuta correctamente, pero la recuperación de la verificación del servidor devuelve una respuesta incorrecta (p.ej., falla la verificación de la firma), invoca PublicKeyCredential.signalUnknownCredential() para evitar que las credenciales huérfanas permanezcan en el proveedor de llaves de acceso.

Ejemplo de código

// optionsFetch and registerVerifyFetch are app-defined server endpoint requests
import { optionsFetch, registerVerifyFetch } from './api.js';

async function triggerConditionalCreate(loginAbortController) {
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (capabilities.conditionalCreate !== true) {
    return; // Platform does not support conditional creation
  }

  // 1. Abort any active autofill conditional-get controllers to clear the WebAuthn pipeline
  loginAbortController.abort();

  // 2. Fetch creation options signaling the backend that this is a conditional request
  const creationOptionsJSON = await optionsFetch({ conditional: true });
  const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);

  let credential;
  try {
    // 3. Invoke silent credentials creation prompt
    credential = await navigator.credentials.create({ 
      publicKey,
      mediation: 'conditional' // Silent background creation mediation
    });
  } catch (e) {
    // 4. Silently swallow common WebAuthn browser exceptions
    if (['InvalidStateError', 'NotAllowedError', 'AbortError'].includes(e.name)) {
      return; 
    }
    console.error('Unexpected conditional create error:', e);
    return;
  }

  // 5. Server verification step using dedicated Try/Catch block
  let encodedResponse = credential.toJSON();
  try {
    const response = await registerVerifyFetch(encodedResponse);
    if (!response.ok) {
      // If the server verification fails, clean up using Signal API
      if (PublicKeyCredential.signalUnknownCredential) {
        await PublicKeyCredential.signalUnknownCredential({
          rpId, // RP ID must match the one defined on the server
          credentialId: encodedResponse.id
        });
      }
    }
  } catch (serverErr) {
    console.error('Verification network failure:', serverErr);
    if (PublicKeyCredential.signalUnknownCredential) {
      await PublicKeyCredential.signalUnknownCredential({
        rpId, // RP ID must match the one defined on the server
        credentialId: encodedResponse.id
      });
    }
  }
}

Estrategias de resguardo

Resguardo de la detección de funciones de llaves de acceso

Instala siempre "webauthn-polyfills" y, luego, impórtalo en el contexto. Ten en cuenta que, siempre que se admita PublicKeyCredential, también se admitirá PublicKeyCredential.getClientCapabilities.

import 'webauthn-polyfills';

Resguardo de sincronización de la API de Signal

La API de Signal de WebAuthn (webauthn-signals) es una optimización progresiva que se usa para mantener los administradores de contraseñas sincronizados con el estado de las credenciales del servidor.

  • Experiencia de resguardo: Se restringe a través de if (PublicKeyCredential.signalUnknownCredential). Si no se admite, la sincronización de la verificación en segundo plano se omite sin problemas y sin arrojar excepciones del navegador.

Resguardo de serialización de JSON sencillo

Instala siempre "webauthn-polyfills" y, luego, impórtalo en el contexto. Ten en cuenta que, siempre que se admita PublicKeyCredential, también se admitirán PublicKeyCredential.parseCreationOptionsFromJSON y PublicKeyCredential.prototype.toJSON.

import 'webauthn-polyfills';