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

TOTP 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 TOTP no Node.js adiciona um segundo fator baseado em códigos temporários gerados por aplicativos autenticadores. O servidor e o dispositivo compartilham um segredo, e ambos calculam um código curto a partir do horário atual.

TOTP melhora a segurança contra senhas vazadas, mas não é resistente a phishing em tempo real. A implementação precisa proteger o segredo, limitar tentativas, lidar com diferença de relógio, oferecer recovery codes e auditar ativação e remoção.

Neste guia, você aprenderá a gerar segredos, criar URI otpauth, exibir QR Code, verificar códigos, cifrar o segredo, evitar replay, implementar recuperação, exigir step-up e testar o fluxo.

O que é TOTP?

TOTP significa Time-Based One-Time Password. O RFC 6238 define o algoritmo. O RFC 4226 define HOTP, base do TOTP.

Para tokens de autenticação, consulte JWT Seguro no Node.js. Para auditoria, veja Logs de Auditoria no Node.js.

Como o código é gerado?

O autenticador calcula um contador com base no horário:

counter = floor(unixTime / period)

O valor padrão comum é 30 segundos. O contador é usado com HMAC e um segredo compartilhado.

Parâmetros comuns

  • algoritmo HMAC, geralmente SHA-1 por compatibilidade;
  • 6 dígitos;
  • período de 30 segundos;
  • pequena janela para diferença de relógio.

Não mude parâmetros sem confirmar suporte dos autenticadores.

Biblioteca

Use uma biblioteca TOTP mantida e auditada. Evite implementar truncamento dinâmico e Base32 manualmente em produção.

Gerando um segredo

const secret = authenticator.generateSecret();

O segredo precisa ter entropia suficiente e ser gerado por fonte criptograficamente segura.

URI otpauth

const uri = authenticator.keyuri(
  user.email,
  'Minha Aplicação',
  secret
);

O formato costuma ser:

otpauth://totp/Minha%20Aplicação:user@example.com?secret=...&issuer=Minha%20Aplicação

QR Code

O frontend pode mostrar o URI como QR Code. O conteúdo contém o segredo e deve ser tratado como credencial.

Não registre a URI

Logs, analytics, ferramentas de sessão e prints de suporte não devem capturar o QR Code ou o valor secret.

Fluxo de ativação

  1. Usuário autenticado solicita ativação.
  2. Servidor exige confirmação recente da senha ou passkey.
  3. Servidor gera segredo pendente.
  4. Usuário escaneia o QR Code.
  5. Usuário informa um código válido.
  6. Servidor ativa TOTP.
  7. Servidor gera recovery codes.
  8. Evento é auditado.

Segredo pendente

Não marque MFA como ativo antes de verificar um código. Armazene estado:

totp_status: 'pending' | 'active' | 'disabled'

Expiração do setup

Um segredo pendente deve expirar após alguns minutos ou horas. Se o usuário reiniciar o fluxo, revogue o segredo anterior.

Verificando o código

const valid = authenticator.verify({
  token: submittedCode,
  secret
});

Normalize apenas espaços permitidos e rejeite valores que não possuem o número esperado de dígitos.

Comparação segura

A biblioteca deve realizar a verificação adequadamente. Não converta códigos para número, pois zeros iniciais são significativos.

Janela de tempo

Uma janela de um passo anterior e posterior tolera relógios levemente diferentes. Janelas grandes aumentam a quantidade de códigos aceitos.

Sincronização do servidor

Use NTP ou serviço equivalente. Se o relógio do servidor diverge, todos os usuários podem falhar.

Evitar replay

Um código permanece válido durante a janela. Para operações críticas, registre o último contador aceito e rejeite reutilização:

if (counter <= user.lastTotpCounter) {
  throw new InvalidCodeError();
}

Atualize o contador atomicamente.

Concorrência

Duas requisições podem enviar o mesmo código ao mesmo tempo. Use transação ou update condicional para garantir uma única aceitação.

Rate limiting

Um código de seis dígitos possui espaço limitado. Restrinja tentativas por usuário, sessão e IP confiável.

Consulte Rate Limiting no Node.js.

Bloqueio progressivo

Depois de falhas, introduza atraso e solicite novo login. Evite bloquear permanentemente a conta por ação de um atacante.

Mensagem de erro

{
  "code": "INVALID_MFA_CODE",
  "message": "Código inválido ou expirado"
}

Não informe se o código pertence à janela anterior ou se o relógio está quase correto.

Armazenando o segredo

O servidor precisa recuperar o segredo para verificar códigos, portanto hash não é suficiente. Use criptografia com chave gerenciada.

Criptografia de envelope

Gere uma chave de dados, cifre o segredo e proteja a chave de dados com KMS ou HSM. Armazene ciphertext, nonce, algoritmo e versão da chave.

Chave fora do banco

Se banco e chave ficam juntos, o benefício da criptografia diminui. Use Secret, KMS ou serviço de chaves com acesso restrito.

Rotação

Versione a chave de criptografia e recifre segredos gradualmente. A rotação do segredo TOTP exige novo enrollment do usuário.

Backups

Backups contêm segredos cifrados. Proteja chaves, acesso e retenção.

Recovery codes

Gere códigos aleatórios de uso único:

const codes = Array.from({ length: 10 }, () =>
  randomBytes(10).toString('base64url')
);

Mostre apenas uma vez.

Hash dos recovery codes

Diferentemente do segredo TOTP, recovery codes podem ser armazenados com hash forte, pois são comparados como senhas.

Uso único

Ao usar um recovery code, marque como consumido na mesma transação que cria a sessão.

Regeneração

Gerar novos recovery codes invalida todos os anteriores e exige autenticação forte.

Removendo TOTP

Exija senha recente, passkey ou outro fator. Não permita remover MFA apenas com a sessão antiga.

Conta comprometida

Se um atacante possui a sessão, ele pode tentar substituir o TOTP. Envie notificação e aplique período de segurança para mudanças críticas.

Step-up authentication

Mesmo em uma sessão autenticada, ações como exportar dados ou alterar cobrança podem exigir um código recente.

if (Date.now() - session.mfaVerifiedAt > 10 * 60 * 1000) {
  throw new MfaRequiredError();
}

Nível de autenticação

Registre na sessão se o usuário entrou apenas com senha ou com MFA. Políticas ABAC podem exigir nível forte.

Veja ABAC no Node.js.

Trusted devices

“Lembrar dispositivo” reduz fricção, mas cria outro token duradouro. Proteja com cookie HttpOnly, Secure, rotação e revogação.

Não use fingerprint invasivo

Características do navegador mudam e podem afetar privacidade. Um token aleatório de dispositivo é mais previsível.

Phishing

TOTP pode ser digitado em um site falso e usado imediatamente pelo atacante. Para resistência a phishing, prefira WebAuthn e passkeys.

Fallback

Oferecer SMS como fallback pode reduzir a segurança. Defina opções de recuperação compatíveis com o risco.

Suporte

Atendentes não devem visualizar o segredo nem desativar MFA sem verificação rigorosa e auditoria.

Multi-tenancy

Uma identidade pode usar o mesmo fator em vários tenants se a conta é global. Decisões de step-up continuam ligadas à sessão e organização.

Sessões

Após verificar TOTP, rotacione o ID de sessão para impedir fixation. O artigo sobre Sessões Seguras aprofunda o tema.

JWT

Um claim como amr pode indicar fatores usados:

{
  "amr": ["pwd", "otp"],
  "auth_time": 1787918400
}

Valide assinatura, issuer e audience.

Auditoria

Registre:

  • setup iniciado;
  • ativação concluída;
  • verificação falha em volume anormal;
  • recovery code usado;
  • códigos regenerados;
  • TOTP removido;
  • step-up concluído;
  • mudança feita por suporte.

Nunca registre o código ou segredo.

Métricas

Monitore taxa de sucesso, falhas, bloqueios, uso de recovery code e remoções. Não use user ID como label.

Alertas

Muitas falhas distribuídas, remoção de MFA e uso de recovery code após mudança de senha podem indicar comprometimento.

Testes unitários

Use um relógio injetável para testar passos de tempo sem depender do horário real.

Teste de janela

test('aceita o passo anterior permitido', () => {
  clock.set('2026-08-28T14:00:30Z');
  const code = generateAt('2026-08-28T14:00:00Z');
  assert.equal(verify(code, secret), true);
});

Teste de replay

Envie o mesmo código duas vezes em paralelo e confirme apenas uma aceitação.

Teste de rate limit

Exceda o número de tentativas e confirme bloqueio temporário sem revelar estado da conta.

Teste de criptografia

Confirme que o banco não contém o segredo em texto claro e que uma chave antiga ainda pode ser lida durante rotação.

Teste de recovery code

Use um código, tente reutilizá-lo e confirme rejeição.

Teste de remoção

Uma sessão sem step-up recente não deve remover TOTP.

Erros comuns

  • Segredo em texto claro: vazamento do banco compromete MFA.
  • QR Code em logs: o segredo é exposto.
  • Sem rate limit: códigos podem ser adivinhados.
  • Janela grande: mais códigos permanecem válidos.
  • Sem proteção contra replay: o mesmo código é reutilizado.
  • Recovery codes sem hash: backup revela acesso.
  • Remoção sem step-up: sessão roubada desativa MFA.

Boas práticas

  • Use biblioteca confiável.
  • Gere segredo criptográfico.
  • Cifre com chave externa.
  • Verifique antes de ativar.
  • Use janela pequena.
  • Limite tentativas.
  • Bloqueie replay.
  • Forneça recovery codes com hash.
  • Exija step-up para mudanças.
  • Audite sem registrar segredos.

Conclusão

Implementar TOTP no Node.js adiciona um segundo fator compatível com aplicativos autenticadores e reduz o impacto de senhas vazadas.

A segurança depende de proteger o segredo, limitar tentativas, controlar replay e oferecer recuperação segura. TOTP ainda pode ser vítima de phishing, portanto aplicações de maior risco devem oferecer WebAuthn ou passkeys. Com criptografia, auditoria e step-up, o fluxo se torna uma camada útil de autenticação sem criar novos segredos expostos.

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