PublicKeyCredential en aplicaciones web
Fecha de publicación: 2023-05-19Este 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:
- el navegador pide un challenge de registro;
- el servidor crea y guarda un challenge aleatorio;
- el navegador pide al autenticador crear o usar claves;
- la persona confirma con PIN, huella o Face ID;
- el navegador manda datos de attestation al servidor;
- el servidor los valida y asocia la clave pública a la persona.
Assertion prueba posesión de un credential registrado:
- el navegador pide un challenge de inicio;
- el servidor crea y guarda un challenge;
- tras verificar a la persona, el autenticador lo firma con la clave privada;
- el navegador envía la assertion;
- 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
- WebAuthn requiere contexto seguro (HTTPS, salvo localhost) y RP ID correcto.
- Los challenges deben ser aleatorios, de un solo uso, breves y ligados a la operación.
- Hay que prever varios credentials, recuperación de acceso y alternativa clara; perder un dispositivo no debe bloquear a la persona.
- No se debe confiar en datos del navegador hasta verificarlos plenamente en servidor.
- En producción conviene usar biblioteca WebAuthn actual y la especificación, no copiar un parser didáctico.
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).