Saltar al contenido
ecdsa.com
Node.js · WebCrypto

SimpleWebAuthn

SimpleWebAuthn de Matthew Miller, la biblioteca de llaves de acceso más usada para TypeScript: generadores de opciones y verificadores de respuestas que exigen la verificación del usuario por defecto, solo aceptan firmas de aserción DER y rechazan los retrocesos del contador.
Licencia
MIT
Versión comprobada
14.0.3 ·
Código fuente
github.com/MasterKale/SimpleWebAuthn
Documentación
simplewebauthn.dev/docs/

Compatibilidad de un vistazo

WebAuthn

SimpleWebAuthn divide WebAuthn en dos paquetes: @simplewebauthn/browser envuelve navigator.credentials, y @simplewebauthn/server genera las opciones que envía una relying party y verifica lo que vuelve: formatos de atestación, claves COSE, contadores y firmas. Para la comprobación, el paquete del servidor recibió las respuestas de un autenticador por software que producía JSON estándar de registro y de aserción: los mismos bytes que se usaron en las páginas de Python, Go y Java.

El error de integración más habitual es una discrepancia entre lo que piden las opciones y lo que exige la verificación. generateRegistrationOptions() usa por defecto userVerification: "preferred", pero ambos verificadores usan por defecto requireUserVerification: true: una aserción de un autenticador que solo demostró la presencia del usuario —una llave de seguridad sin PIN— se rechazó con User verification required, but user could not be verified. Decide una vez y haz que ambos lados digan lo mismo, como hace el ejemplo.

Las firmas de aserción ES256 deben ser DER ASN.1, como exige la especificación WebAuthn. Una firma r‖s en bruto de 64 bytes llega igualmente al analizador ASN.1 y falla con un mensaje que depende de los bytes de la firma —Too big integer, End of input reached before message was fully decoded o Data does not match to ECDSASigValue ASN1 schema. fueron los más frecuentes con 60 credenciales nuevas—, y ninguno menciona la codificación. Los autenticadores y los proveedores de llaves de acceso producen DER, así que las firmas en bruto suelen indicar un cliente escrito a mano o un arnés de pruebas que llamó directamente al sign() de WebCrypto; conviértelas con el conversor DER ⇄ en bruto. Una firma DER high-S se verificó.

Un contador de firmas menor que el almacenado es un error, no una advertencia: la verificación lanzó Response counter value 3 was lower than expected 5, y un contador de 0 tras un 5 almacenado falló de la misma manera. Solo una credencial que siempre ha devuelto 0 —como hacen las llaves de acceso sincronizadas— pasa con 0. Guarda el contador recibido y decide tú si un retroceso bloquea la credencial o solo genera una alerta. La versión 14 pone ML-DSA-44 (COSE -48) en primer lugar en los pubKeyCredParams predeterminados, por delante de Ed25519 (-8), ES256 (-7) y RS256 (-257), y la credencial Ed25519 se verificó de principio a fin.

Decodifica lo que envió realmente un navegador con el inspector de WebAuthn, prueba flujos sin un autenticador real en el entorno de pruebas de llaves de acceso e identifica modelos de autenticador por su AAGUID con la búsqueda de AAGUID.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/simplewebauthn/run.sh ejecutó SimpleWebAuthn 14.0.3 en Node.js 24.22.0 en la imagen de contenedor node:24.22.0-bookworm-slim el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Algoritmos COSE predeterminados

    -48-8-7-257

    generateRegistrationOptions() enumera los algoritmos COSE -48 (ML-DSA-44), -8 (Ed25519), -7 (ES256) y -257 (RS256), en ese orden.

    Registrado por la comprobación
    fact webauthn-default-algorithms = -48, -8, -7, -257
  • Atestación predeterminada

    none

    La preferencia de transmisión de la atestación es none por defecto.

    Registrado por la comprobación
    fact webauthn-attestation = none
  • Verificación del usuario predeterminada

    preferred

    authenticatorSelection.userVerification es preferred por defecto.

    Registrado por la comprobación
    fact webauthn-user-verification = preferred
  • Clave residente predeterminada

    preferred

    authenticatorSelection.residentKey es preferred por defecto.

    Registrado por la comprobación
    fact webauthn-resident-key = preferred
  • Verificación del usuario exigida por defecto

    yes

    Ambos verificadores exigen el indicador UV salvo que se pase requireUserVerification: false: un registro y una aserción con solo UP activado se rechazaron con User verification was required, but user could not be verified y User verification required, but user could not be verified.

    Registrado por la comprobación
    fact webauthn-uv-enforced-by-default = yes
    evidence webauthn-uv-enforced-by-default: registration with the UV flag clear: throws User verification was required, but user could not be verified
  • Codificación de la firma de la aserción

    der

    Una aserción ES256 correcta con una firma DER ASN.1 se verificó.

    Registrado por la comprobación
    fact webauthn-assertion-encoding = der
  • Firma de aserción r‖s en bruto

    rejected

    La misma aserción con una firma r‖s en bruto de 64 bytes lanzó un error de análisis ASN.1 cuyo texto depende de los bytes de la firma.

    Registrado por la comprobación
    fact webauthn-raw-signature = rejected
  • Firma de aserción high-S

    accepted

    Una firma DER con s sustituido por n − s se verificó.

    Registrado por la comprobación
    fact webauthn-high-s = accepted
  • Contador de firmas que retrocede

    rejected

    Un contador inferior al valor almacenado lanzó Response counter value 3 was lower than expected 5; un contador de 0 tras un 5 almacenado también se rechazó, mientras que 0 tras un 0 almacenado se verificó.

    Registrado por la comprobación
    fact webauthn-counter-regression = rejected
    evidence webauthn-counter-regression: counter 0 after a stored 5: throws Response counter value 0 was lower than expected 5; counter 0 after a stored 0: verified
  • Credenciales Ed25519

    supported

    Una credencial Ed25519 (COSE -8) se registró y se autenticó correctamente.

    Registrado por la comprobación
    fact webauthn-ed25519 = supported
    evidence webauthn-ed25519: verified

Uso

Genera opciones de registro y de autenticación que piden la misma política de verificación del usuario que los verificadores aplican por defecto.
import { generateAuthenticationOptions, generateRegistrationOptions } from "@simplewebauthn/server";

const rpID = "example.com";

// Registration: the options ask for UV only as "preferred"...
const registration = await generateRegistrationOptions({
  rpName: "Example",
  rpID,
  userName: "user@example.com",
  authenticatorSelection: { residentKey: "required", userVerification: "required" },
});

// ...while verifyRegistrationResponse() and verifyAuthenticationResponse()
// require the UV flag by default. Ask for "required" up front (as here), or pass
// requireUserVerification: false when a security key without a PIN is acceptable.
const authentication = await generateAuthenticationOptions({ rpID, userVerification: "required" });

console.log("algorithms:", registration.pubKeyCredParams.map((param) => param.alg).join(", "));
console.log("uv:", registration.authenticatorSelection?.userVerification, authentication.userVerification);
console.log("challenge bytes:", Buffer.from(authentication.challenge, "base64url").length);

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor node:24.22.0-bookworm-slim, y la ejecución falla si la salida no coincide.

Configuración
npm init -y >/dev/null && npm install --no-audit --no-fund @simplewebauthn/server@14.0.3
Comando
node usage.mts
La salida debe coincidir (expresión regular)
algorithms: -48, -8, -7, -257\nuv: required required\nchallenge bytes: 32

Bibliotecas