PublicKeyCredential en aplicaciones web

Fecha de publicación: 2023-05-19

Este ejemplo muestra autenticación de usuario mediante PublicKeyCredential del Web API. Ambas capas de la demostración están en JavaScript; el código está en flancer64/demo-webauthn-pubkey y la demo en pk.auth.demo.teqfw.com.

Participantes de WebAuthn

Intervienen usuario, navegador, backend (Relying Party), almacenamiento y autenticador. Un autenticador es un dispositivo físico o programa con claves asimétricas al que el navegador puede acceder: biometría/PIN integrado, llave de seguridad o autenticador virtual para desarrollo.

En Chrome se activa el autenticador virtual en DevTools → More tools → WebAuthn. Resulta útil para estudiar el flujo, pero no sustituye pruebas en dispositivos y navegadores reales.

Dos procesos: attestation y assertion

Attestation registra un credential y su clave pública en servidor:

  1. el navegador pide un challenge de registro;
  2. el servidor crea y guarda un challenge aleatorio;
  3. el navegador pide al autenticador crear o usar claves;
  4. la persona confirma con PIN, huella o Face ID;
  5. el navegador manda datos de attestation al servidor;
  6. el servidor los valida y asocia la clave pública a la persona.

Assertion prueba posesión de un credential registrado:

  1. el navegador pide un challenge de inicio;
  2. el servidor crea y guarda un challenge;
  3. tras verificar a la persona, el autenticador lo firma con la clave privada;
  4. el navegador envía la assertion;
  5. el servidor verifica la firma con la clave pública guardada.

La clave privada nunca sale del autenticador. El servidor conserva ID de credential, clave pública y metadatos necesarios para verificar.

Registrar una clave

El servidor genera un challenge criptográficamente aleatorio, por ejemplo 32 bytes con crypto.randomBytes, lo guarda con vida limitada y lo entrega al navegador en base64url junto con datos de usuario.

const attestation = await navigator.credentials.create({
  publicKey: {
    rp: { name: 'WebAuthn Demo' },
    user: { id: userIdBytes, name: email, displayName: email },
    challenge: challengeBytes,
    pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'preferred',
    },
  },
});

Los binarios attestationObject y clientDataJSON viajan al servidor en base64url. El servidor debe verificar challenge, origin, rpId, tipo de operación, flags de user verification y formato de attestation. Extrae la clave pública, por ejemplo JWK, y la guarda con el credential ID. cbor y parse-cosekey ayudan a procesar los datos de demostración.

Inicio por assertion

Para iniciar sesión, el backend crea un challenge nuevo y de un solo uso para el credential. El navegador llama:

const assertion = await navigator.credentials.get({
  publicKey: {
    challenge: challengeBytes,
    allowCredentials: [{ id: credentialIdBytes, type: 'public-key', transports: ['internal'] }],
  },
});

Al servidor se envían authenticatorData, clientDataJSON y signature. La validación debe comparar challenge guardado, origin y rpId, revisar flags, verificar firma con la clave pública y tratar el signature counter según la política del producto. Después la aplicación crea su propia sesión: WebAuthn no sustituye gestión de sesión ni autorización.

Requisitos prácticos

Conclusión

PublicKeyCredential permite un inicio sin contraseña donde el secreto queda en el autenticador y el servidor verifica una prueba criptográfica. La demo ayuda a comprender intercambio de challenge y credential. En un producto real también importan UX de registro, gestión de claves, recuperación segura y validación estricta en servidor. Wiredgeese puede diseñar e implantar este flujo en una aplicación web existente.

Fragmentos adicionales de código fuente

} * Demo_Back_Web_Api_Sign_Up * Fl32_Auth_Back_Mod_PubKey.attestChallengeCreate
/** @type {PublicKeyCredential} */
const attestation = await navigator.credentials.create({publicKey});
{
},
},
{
}
],
}
}
} * Demo_Front_Ui_Route_Sign_Up * Fl32_Auth_Front_Mod_PubKey.composeOptPkCreate
{
}
} * Demo_Front_Ui_Route_Sign_Up * Fl32_Auth_Front_Mod_PubKey.attest * Fl32_Auth_Back_Web_Api_Attest
{
} On the frontend, the attestation ID (DDn…6XA) is stored in local storage for later use in assertions.
{“attestationId”: “DDn8LhxnQB8g7qNKngMy-noDzSDIOyUMGg2soOeS6XA”} * Demo_Front_Ui_Route_Sign_In * Fl32_Auth_Front_Mod_PubKey.assertChallenge * Fl32_Auth_Back_Web_Api_Assert_Challenge
{
} * Fl32_Auth_Back_Web_Api_Assert_Challenge * Fl32_Auth_Back_Mod_PubKey.assertChallengeCreate
/** @type {PublicKeyCredential} */
const assertion = await navigator.credentials.get({publicKey});
{
{
]
}
]
}
} * Demo_Front_Ui_Route_Sign_In * Fl32_Auth_Front_Mod_PubKey.composeOptPkGet
{
} * Demo_Front_Ui_Route_Sign_In * Fl32_Auth_Front_Mod_PubKey.validate * Fl32_Auth_Back_Web_Api_Assert_Validate
{
} Then, retrieve the user’s public key from the database for the corresponding challenge (refer to step 2).