Reautenticación con llave de acceso

Funciones web: Autenticación web

En esta guía centrada en los cambios, se detalla cómo implementar la autenticación de aumento de privilegios o la nueva verificación para un usuario que accedió a su cuenta antes de que realice cambios sensibles en ella (p.ej., actualizaciones de contraseñas o transferencias financieras).

Arquitectura de Delta Flow

A diferencia de la autenticación normal, la reautenticación con llave de acceso restringe los mensajes de diálogo de la llave de acceso estrictamente a las credenciales registradas previamente del usuario que accedió para evitar la mezcla de cuentas o la falsificación de llaves de acceso durante las sesiones activas.

Del servidor

Delta de generación de opciones

Crea un extremo que complete los parámetros de credenciales permitidas específicamente para el usuario activo y conocido:

Restringe las credenciales: Completa el array de opciones allowCredentials con registros PublicKeyCredentialDescriptor específicos que asignen todos los IDs de credenciales registrados para el usuario que accedió. Si dejas este campo vacío o lo omites, se volverá a las credenciales detectables, lo que infringe la seguridad de la sesión.

// Node.js step-up options generation example
router.post("/api/reauth/options", enforceActiveSession, async (req, res) => {
  const userPasskeys = await db.findCredentialsByUserId(req.user.id);

  const options = {
    challenge: serverGeneratedBase64UrlChallenge, // Random challenge stored in user session
    rpId: "example.com",
    // Enforce allowance strictly limited to the user's credentials list
    allowCredentials: userPasskeys.map((cred) => ({
      type: "public-key",
      id: cred.id,
      transports: cred.transports, // Speeds up resolution by indicating platform transports
    })),
  };
  return res.json(options);
});

Delta del extremo de verificación

Verifica la aserción que devuelve el cliente:

Verificar la propiedad de la cuenta: El extremo de verificación DEBE verificar de forma explícita que el ID de credencial autenticado resultante que devuelve el cliente se resuelva en un registro de credencial almacenado cuyo ID de usuario asociado coincida estrictamente con el usuario que accedió de forma activa (storedCredential.passkeyUserId === req.user.id). Si se devuelve una llave de acceso válida de un usuario diferente, la autenticación DEBE rechazarse de inmediato.

Deltas de flujo del cliente

Las aplicaciones eligen entre dos interfaces de reautenticación según la IU de la transacción:

A. Flujo de botones (sin campos de entrada)

Activa la reautenticación cuando un usuario presione un botón "Verificar identidad" o "Continuar con la transacción".

<button id="reauth-btn" data-testid="reauth-button">Confirm Transaction</button>
let reauthAbortController = new AbortController();

async function triggerButtonReauth() {
  // Abort any background suggestion flows to avoid passkey prompt collisions
  reauthAbortController.abort();
  reauthAbortController = new AbortController();

  const optionsResponse = await fetch("/api/reauth/options", {
    method: "POST",
  });
  const optionsJSON = await optionsResponse.json();
  const publicKey =
    PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);

  try {
    const credential = await navigator.credentials.get({
      publicKey,
      signal: reauthAbortController.signal,
    });

    if (credential) {
      const encodedCredential = credential.toJSON();
      const verifyResponse = await fetch("/api/reauth/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(encodedCredential),
      });

      if (verifyResponse.ok) {
        showTransactionSuccessUI();
      } else if (verifyResponse.status === 404 && PublicKeyCredential.signalUnknownCredential) {
        await PublicKeyCredential.signalUnknownCredential({
          rpId, // RP ID must match the one defined on the server
          credentialId: encodedCredential.id
        });
      }
    }
  } catch (err) {
    if (err.name === "NotAllowedError") {
      console.log("User cancelled reauthentication.");
    }
  }
}

document
  .getElementById("reauth-btn")
  .addEventListener("click", triggerButtonReauth);

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 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.parseRequestOptionsFromJSON y PublicKeyCredential.prototype.toJSON.

import 'webauthn-polyfills';