Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

WebAuthn no Node.js: Guia Prático

Atualizado em: 28 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Implementar WebAuthn no Node.js permite autenticação com chaves criptográficas vinculadas ao domínio. O navegador e um autenticador, como celular, chave de segurança ou biometria do dispositivo, criam um par de chaves. A chave privada permanece protegida no autenticador, enquanto o servidor armazena apenas a chave pública.

WebAuthn é resistente a phishing porque a credencial só funciona para o RP ID correto. O fluxo exige challenges aleatórios, validação rigorosa de origin, verificação de assinatura, controle de contador, recuperação de conta e armazenamento adequado das credenciais.

Neste guia, você aprenderá registration, authentication, challenge, RP ID, origin, user verification, attestation, sign count, bibliotecas, sessões, auditoria e testes.

O que é WebAuthn?

WebAuthn é uma API da Web para autenticação por credenciais de chave pública. A especificação Web Authentication do W3C descreve o protocolo. A documentação WebAuthn na MDN apresenta a API do navegador.

Para autenticação temporal, consulte TOTP no Node.js. Para tokens de API, veja JWT Seguro no Node.js.

Componentes

  • Relying Party: sua aplicação.
  • Cliente: navegador ou sistema operacional.
  • Autenticador: dispositivo que protege a chave privada.
  • Credential: chave pública associada ao usuário e domínio.

RP ID

O RP ID normalmente é o domínio, como:

example.com

Ele não inclui protocolo, porta ou caminho. Uma credencial criada para um domínio não deve autenticar outro domínio não relacionado.

Origin

O servidor valida a origem completa esperada:

https://app.example.com

Ambientes de desenvolvimento, homologação e produção precisam de configurações separadas.

HTTPS

WebAuthn exige contexto seguro, com exceções locais específicas para desenvolvimento. Produção deve usar HTTPS válido.

Fluxo de registro

  1. Usuário inicia cadastro de credencial.
  2. Servidor gera challenge aleatório.
  3. Servidor retorna opções de criação.
  4. Navegador chama navigator.credentials.create().
  5. Autenticador cria o par de chaves.
  6. Cliente envia a resposta ao servidor.
  7. Servidor valida challenge, origin, RP ID e attestation.
  8. Servidor salva chave pública e metadados.

Gerando challenge

const challenge = randomBytes(32)
  .toString('base64url');

O challenge deve ser imprevisível, de uso único e expirar rapidamente.

Armazenando challenge

Associe à sessão e ao propósito:

{
  "challenge": "...",
  "type": "webauthn_registration",
  "userId": "...",
  "expiresAt": "..."
}

Não aceite um challenge de autenticação em um fluxo de registro.

Opções de registro

const options = await generateRegistrationOptions({
  rpName: 'Minha Aplicação',
  rpID: 'example.com',
  userID: user.id,
  userName: user.email,
  attestationType: 'none',
  authenticatorSelection: {
    residentKey: 'preferred',
    userVerification: 'preferred'
  },
  excludeCredentials: existingCredentials
});

A API exata depende da biblioteca.

User ID

Use um identificador opaco e estável, não e-mail mutável. Evite dados pessoais desnecessários no handle.

excludeCredentials

Informe credenciais já cadastradas para evitar duplicação no mesmo autenticador quando aplicável.

Attestation

Attestation fornece informações sobre o autenticador. Para a maioria das aplicações, none reduz coleta e complexidade.

Attestation empresarial

Ambientes corporativos podem exigir dispositivos certificados. Isso precisa de política, metadata service e revisão de privacidade.

Verificando registro

const verification = await verifyRegistrationResponse({
  response: body,
  expectedChallenge: session.challenge,
  expectedOrigin: 'https://app.example.com',
  expectedRPID: 'example.com'
});

Só salve a credencial se verified for verdadeiro.

Dados da credencial

CREATE TABLE webauthn_credentials (
  id TEXT PRIMARY KEY,
  user_id UUID NOT NULL,
  public_key BYTEA NOT NULL,
  counter BIGINT NOT NULL,
  transports TEXT[],
  device_type TEXT,
  backed_up BOOLEAN,
  name TEXT,
  created_at TIMESTAMPTZ NOT NULL,
  last_used_at TIMESTAMPTZ
);

Credential ID

Armazene no formato binário ou Base64URL de maneira consistente. Não altere padding entre leitura e verificação.

Chave pública

A chave pública não é segredo, mas deve ser protegida contra alteração. Uma substituição permitiria autenticação do atacante.

Fluxo de autenticação

  1. Servidor gera challenge.
  2. Servidor retorna opções de autenticação.
  3. Navegador chama navigator.credentials.get().
  4. Autenticador confirma presença ou verificação do usuário.
  5. Servidor valida assinatura e contexto.
  6. Servidor cria ou eleva a sessão.

Opções de autenticação

const options = await generateAuthenticationOptions({
  rpID: 'example.com',
  allowCredentials: credentials,
  userVerification: 'preferred'
});

Login identificável

O usuário informa e-mail primeiro, e o servidor envia as credenciais permitidas daquela conta.

Login sem username

Credenciais discoverable permitem que o autenticador selecione a conta. Nesse caso, allowCredentials pode ser omitido.

Verificando autenticação

const verification = await verifyAuthenticationResponse({
  response: body,
  expectedChallenge: session.challenge,
  expectedOrigin,
  expectedRPID,
  credential: {
    id: credential.id,
    publicKey: credential.publicKey,
    counter: credential.counter,
    transports: credential.transports
  }
});

User presence

User Presence confirma interação, como tocar a chave. Nem sempre prova biometria ou PIN.

User verification

User Verification confirma que o autenticador verificou a pessoa, usando biometria, PIN ou desbloqueio equivalente.

required, preferred ou discouraged

Para operações sensíveis, use required. Para compatibilidade ampla, preferred pode ser adequado.

Sign counter

Alguns autenticadores incrementam um contador. Um valor menor ou igual ao anterior pode indicar clonagem, mas autenticadores sincronizados podem usar comportamento diferente.

Atualização atômica

UPDATE webauthn_credentials
SET counter = $1,
    last_used_at = now()
WHERE id = $2
  AND counter = $3;

Trate concorrência e políticas da biblioteca.

Credenciais sincronizadas

Passkeys podem ser sincronizadas entre dispositivos. O campo backup state ajuda a entender o tipo, mas não deve ser usado sozinho para bloquear acesso.

Challenge de uso único

Depois de uma tentativa, remova o challenge, mesmo quando a assinatura falha. Isso reduz replay.

Expiração

Use poucos minutos. Um challenge não precisa sobreviver indefinidamente.

Rate limiting

Limite tentativas por conta, sessão e IP. WebAuthn não é vulnerável a adivinhação como senha, mas endpoints ainda podem ser abusados.

Consulte Rate Limiting no Node.js.

Sessão após autenticação

Rotacione o ID da sessão e registre o nível de autenticação. Não preserve uma sessão pré-login com o mesmo identificador.

Step-up

WebAuthn pode ser exigido novamente antes de exportar dados, alterar cobrança ou remover outro fator.

Várias credenciais

Permita cadastrar celular, computador e chave de segurança. Isso reduz risco de bloqueio da conta.

Nome amigável

MacBook pessoal
Chave USB do trabalho
Celular

O nome é fornecido pelo usuário e precisa ser escapado na interface.

Removendo credencial

Exija autenticação recente e mantenha ao menos um método de recuperação ou outra credencial.

Última credencial

Não remova o último método de acesso sem confirmar senha, recovery code ou fluxo de recuperação robusto.

Recuperação de conta

WebAuthn é forte, mas a recuperação pode se tornar o elo fraco. Use verificação proporcional ao risco, período de espera e notificações.

Recovery codes

Códigos de uso único podem ser armazenados com hash. O uso deve ser auditado e invalidar sessões suspeitas.

TOTP como fallback

TOTP oferece compatibilidade, mas reduz resistência a phishing. Mostre claramente a diferença de segurança.

Bibliotecas

Use uma biblioteca WebAuthn ativa que valide estruturas CBOR, COSE, clientDataJSON e authenticatorData. Não implemente a criptografia manualmente.

Atualizações da especificação

WebAuthn evolui. Fixe versões da biblioteca, acompanhe avisos de segurança e teste navegadores suportados.

Frontend

O navegador trabalha com ArrayBuffer. Converta Base64URL corretamente antes de chamar a API.

Base64URL

Não use Base64 comum sem tratar +, / e padding. Use utilitários da biblioteca.

AbortController

Permita cancelar o prompt quando o usuário fecha a tela ou inicia outro fluxo.

Conditional UI

Navegadores modernos podem mostrar passkeys no campo de login com mediação condicional. Detecte suporte e ofereça fallback.

Domínios e subdomínios

Escolher RP ID como domínio principal pode permitir credenciais em subdomínios elegíveis. Avalie isolamento e risco de subdomain takeover.

Ambientes separados

Não reutilize credenciais de produção em homologação. RP ID e origin devem ser diferentes.

Proxy reverso

Construa origin a partir de configuração confiável, não de Host ou X-Forwarded-Proto arbitrários.

Multi-tenancy

Credenciais geralmente pertencem à identidade global. Se cada tenant usa domínio customizado, o desenho de RP ID precisa ser avaliado.

Auditoria

Registre:

  • cadastro iniciado e concluído;
  • credencial usada;
  • falha de verificação;
  • credencial removida;
  • nome alterado;
  • recovery usado;
  • step-up concluído.

Não registre challenge, assinatura completa ou dados biométricos.

Consulte Logs de Auditoria no Node.js.

Métricas

Monitore sucesso, falhas, cadastros, credenciais por conta, recovery e incompatibilidade de navegador.

Alertas

Remoção de todas as credenciais, recuperação seguida de mudança de e-mail e múltiplas falhas podem exigir revisão.

Testes unitários

Mock apenas a camada da biblioteca. Para o fluxo completo, use navegador real ou ferramentas de automação com autenticador virtual.

Teste de challenge

Confirme rejeição de challenge expirado, reutilizado ou criado para outro usuário.

Teste de origin

Envie uma resposta com origin diferente e confirme rejeição.

Teste de RP ID

Credenciais de outro RP ID não podem autenticar.

Teste de contador

Simule contador regressivo e confirme a política definida sem bloquear passkeys sincronizadas indevidamente.

Teste de sessão

Após sucesso, o ID deve ser rotacionado e o nível de autenticação atualizado.

Teste de remoção

Uma sessão antiga ou sem step-up não deve remover a última credencial.

Erros comuns

  • Challenge reutilizável: respostas podem ser repetidas.
  • Origin derivada do request: headers forjados alteram validação.
  • RP ID incorreto: credenciais falham ou abrangem domínio indevido.
  • Chave pública alterável: atacante substitui a credencial.
  • Sem recuperação: usuário perde a conta.
  • Remoção sem step-up: sessão roubada elimina fatores.
  • Implementação manual: parsing e criptografia ficam vulneráveis.

Boas práticas

  • Use biblioteca confiável.
  • Gere challenges criptográficos.
  • Expira e consuma cada challenge.
  • Valide origin e RP ID por configuração.
  • Proteja a chave pública contra alteração.
  • Rotacione a sessão.
  • Permita várias credenciais.
  • Exija step-up para mudanças.
  • Planeje recuperação.
  • Teste com autenticador virtual.

Conclusão

Implementar WebAuthn no Node.js permite autenticação resistente a phishing sem armazenar um segredo reutilizável no servidor. A chave privada permanece no autenticador, e a aplicação verifica assinaturas com a chave pública.

A segurança depende de challenges de uso único, origin e RP ID fixos, bibliotecas confiáveis e recuperação de conta robusta. Com sessões rotacionadas, múltiplas credenciais e auditoria, WebAuthn oferece uma base forte para MFA e autenticação sem senha.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita