O SimpleWebAuthn divide o WebAuthn em dois pacotes: @simplewebauthn/browser encapsula navigator.credentials, e @simplewebauthn/server gera as opções que uma parte confiável envia e verifica o que volta — formatos de atestado, chaves COSE, contadores e assinaturas. O teste usou o pacote de servidor com um autenticador em software que produziu JSON padrão de registro e de asserção, os mesmos bytes usados nas páginas de Python, Go e Java.
O bug de integração mais comum é a divergência entre o que as opções pedem e o que a verificação exige. Por padrão, generateRegistrationOptions() usa userVerification: "preferred", mas os dois verificadores usam requireUserVerification: true: uma asserção de um autenticador que só comprovou a presença do usuário — uma chave de segurança sem PIN — foi recusada com User verification required, but user could not be verified. Decida uma vez e faça os dois lados dizerem a mesma coisa, como faz o exemplo.
As assinaturas de asserção com ES256 precisam estar em ASN.1 DER, como exige a especificação WebAuthn. Uma assinatura r‖s bruta de 64 bytes é passada ao parser ASN.1 mesmo assim e falha com uma mensagem que depende dos bytes da assinatura — Too big integer, End of input reached before message was fully decoded ou Data does not match to ECDSASigValue ASN1 schema. foram as mais frequentes em 60 credenciais novas —, e nenhuma delas menciona a codificação. Autenticadores e provedores de chaves de acesso produzem DER, então assinaturas brutas costumam indicar um cliente escrito à mão ou um código de teste que chamou o sign() do WebCrypto diretamente; converta-as com o conversor DER ⇄ bruto. Uma assinatura DER high-S foi verificada.
Um contador de assinaturas menor que o armazenado é um erro, não um aviso: a verificação lançou Response counter value 3 was lower than expected 5, e um contador 0 depois de um 5 armazenado falhou da mesma forma. Só uma credencial que sempre informou 0 — como fazem as chaves de acesso sincronizadas — passa com 0. Armazene o contador recebido e decida você mesmo se um retrocesso bloqueia a credencial ou só gera um alerta. A versão 14 coloca o ML-DSA-44 (COSE -48) em primeiro lugar no pubKeyCredParams padrão, à frente de Ed25519 (-8), ES256 (-7) e RS256 (-257), e a credencial Ed25519 foi verificada de ponta a ponta.
Decodifique o que um navegador realmente enviou com o inspetor de WebAuthn, experimente fluxos sem um autenticador real no ambiente de testes de chaves de acesso e identifique modelos de autenticador pelo AAGUID com a consulta de AAGUID.