O que é MFA e onde entra no fluxo de login
MFA (Multi-Factor Authentication) adiciona uma etapa extra de verificação além do fator primário (geralmente senha). No back-end, isso significa que o login passa a ter pelo menos dois estágios: (1) validar credenciais primárias e (2) validar um desafio de segundo fator. O ponto central é modelar esse “meio-login” com segurança, sem emitir uma sessão/token final antes do MFA ser concluído.
Um fluxo típico fica assim:
- Etapa 1 (primary): usuário envia identificador + senha.
- Decisão: se o usuário tem MFA habilitado e o dispositivo não é confiável, o back-end cria um desafio pendente e responde que MFA é necessário.
- Etapa 2 (MFA): usuário prova posse do segundo fator (TOTP, WebAuthn/passkey, SMS em último caso, ou código de recuperação).
- Finalização: somente após sucesso no MFA o back-end emite o estado autenticado final (sessão/cookie ou token).
Fatores e métodos principais (e quando usar)
TOTP (Time-based One-Time Password)
TOTP é o código de 6–8 dígitos gerado por um app autenticador a partir de um segredo compartilhado e do tempo. É amplamente suportado e funciona offline.
- Prós: simples, barato, offline.
- Contras: vulnerável a phishing e “real-time relay” (o atacante captura e usa o código imediatamente).
- Onde entra: após a senha, o usuário informa o código TOTP para concluir o login.
WebAuthn / Passkeys
WebAuthn (passkeys) usa criptografia assimétrica e vincula a autenticação ao domínio (origin), reduzindo drasticamente phishing. Em vez de “código”, o usuário aprova com biometria/PIN no dispositivo, e o navegador/app envia uma asserção assinada.
- Prós: forte contra phishing, sem segredos compartilhados no servidor, melhor UX.
- Contras: integração mais complexa; requer suporte do cliente (browser/OS).
- Onde entra: pode ser usado como segundo fator após senha, ou como fator primário (passwordless). Neste capítulo, foque como segundo fator.
SMS (último recurso)
SMS entrega um código por mensagem. Deve ser tratado como opção de contingência, não como padrão, por riscos como SIM swap, interceptação e engenharia social.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
- Prós: acessível para usuários sem app autenticador.
- Contras: segurança inferior; dependência de operadora; suscetível a sequestro de número.
- Onde entra: segundo fator quando não há TOTP/WebAuthn disponível.
Códigos de recuperação
Códigos de recuperação são “senhas de uso único” geradas quando o usuário habilita MFA. Servem para recuperar acesso quando o segundo fator não está disponível.
- Boas práticas: gerar 8–12 códigos, exibir uma única vez, armazenar apenas hash no servidor, invalidar após uso.
Modelagem de estados no back-end
Para MFA ser robusto, o back-end precisa representar explicitamente estados intermediários e artefatos relacionados. Abaixo está um modelo prático (independente de framework) para orientar tabelas/coleções e lógica.
Estado do usuário: MFA habilitado
Campos típicos no registro do usuário:
mfa_enabled(boolean)mfa_methods(lista:webauthn,totp,sms)mfa_preferred_method(opcional)mfa_enrolled_at,mfa_last_verified_at(auditoria e políticas)
Desafio pendente (MFA challenge)
Após validar a senha, não emita autenticação final. Em vez disso, crie um registro de desafio pendente com TTL curto (ex.: 5 minutos). Esse desafio é o “ticket” que permite tentar MFA.
Campos recomendados:
challenge_id(UUID aleatório)user_idstatus(pending,verified,expired,locked)created_at,expires_atattempt_count,last_attempt_atip,user_agent(para detecção e auditoria)allowed_methods(ex.:["webauthn","totp","recovery"])context(ex.:login,step_up)
O cliente recebe challenge_id e sabe que precisa concluir MFA. Esse challenge_id não deve ser previsível e deve ser tratado como segredo temporário.
Dispositivos confiáveis (trusted devices)
“Confiar neste dispositivo por X dias” reduz fricção sem remover MFA globalmente. Modele isso como um token de confiança por dispositivo, com revogação e expiração.
Estratégia comum:
- Após MFA bem-sucedido, se o usuário optar por confiar, o servidor cria um
trusted_devicee emite um cookie/identificador persistente. - Em logins futuros, se o cookie for válido e não revogado, o servidor pode pular o MFA (ou exigir “step-up” apenas para ações sensíveis).
Campos recomendados para trusted_devices:
device_id(UUID)user_iddevice_fingerprint_hint(opcional; não confie apenas nisso)token_hash(hash do token persistente)created_at,expires_at,revoked_atlast_used_at,ip_last
Boas práticas: o token do dispositivo deve ser aleatório, armazenado como hash no servidor, e rotacionado periodicamente (por exemplo, a cada uso ou a cada N dias).
Segredos TOTP
Para TOTP, o servidor precisa armazenar o segredo compartilhado. Isso exige proteção extra:
- Criptografia em repouso: armazene o segredo cifrado (ex.: AES-GCM) com uma chave gerenciada fora do banco (KMS/HSM quando possível).
- Separação de chaves: não deixe a chave de criptografia no mesmo lugar que o banco.
- Controle de acesso: limite quem/qual serviço pode descriptografar.
- Rotação: planeje rotação de chaves de criptografia (envelope encryption ajuda).
Campos sugeridos:
totp_secret_ciphertexttotp_secret_kid(identificador da chave usada)totp_confirmed(somente após o usuário validar um código durante o cadastro)
Credenciais WebAuthn
Para WebAuthn, você armazena a credencial pública e metadados, não um segredo reutilizável:
credential_id(bytes/base64url)public_keysign_count(para detecção de clonagem em alguns cenários)transports(opcional)created_at,last_used_at
Códigos de recuperação
Armazene apenas hash (como senhas), com marcação de uso:
code_hashused_at(nulo até ser usado)created_at
Riscos e boas práticas específicas de MFA
Rate limit e bloqueio progressivo em desafios
Mesmo com MFA, um atacante pode tentar adivinhar TOTP/SMS ou abusar do endpoint de desafio. Aplique controles:
- Rate limit por
challenge_id,user_id, IP e faixa de IP. - Limite de tentativas: ex.: 5 tentativas por desafio; após isso, marque como
lockede exija novo login. - Backoff: aumente o tempo entre tentativas (ex.: 1s, 2s, 4s...).
- TTL curto: desafios expiram rápido (ex.: 5 min).
Proteção contra phishing: preferir WebAuthn
TOTP e SMS podem ser capturados por páginas falsas e usados em tempo real. WebAuthn reduz esse risco porque a asserção é vinculada ao origin (domínio) e não pode ser “reaproveitada” em outro site. Boas práticas:
- Ofereça WebAuthn como método preferencial quando disponível.
- Step-up para ações sensíveis: mesmo em sessão ativa, peça WebAuthn para trocar e-mail, sacar valores, alterar MFA etc.
- Evite “fallback” automático para SMS; exija confirmação explícita e monitore.
Armazenamento seguro de segredos TOTP
Além de criptografia em repouso, considere:
- Minimização: só descriptografar o segredo no momento de validar o código.
- Auditoria: registre eventos de leitura/uso (sem logar o segredo).
- Proteção de backup: backups do banco devem estar cifrados e com acesso restrito.
Auditoria de eventos (observabilidade de segurança)
Registre eventos para investigação e detecção:
mfa.enroll.started,mfa.enroll.completedmfa.challenge.created,mfa.challenge.succeeded,mfa.challenge.failed,mfa.challenge.lockedmfa.method.added,mfa.method.removedmfa.trusted_device.added,mfa.trusted_device.revokedmfa.recovery_code.used,mfa.recovery_codes.regenerated
Inclua user_id, IP, user-agent, timestamps e um request_id para correlação. Evite registrar códigos, segredos, ou payloads sensíveis.
Passo a passo prático: habilitar MFA (TOTP e WebAuthn)
Cadastro TOTP (enrollment)
Objetivo: o usuário escaneia um QR code e confirma com um código válido antes de marcar TOTP como ativo.
Iniciar: servidor gera um segredo TOTP aleatório e cria um registro de enrollment pendente.
Exibir QR: servidor retorna um
otpauth://(ou QR) para o app autenticador.Confirmar: usuário envia um código TOTP; servidor valida e então marca
totp_confirmed=trueemfa_enabled=true.
// Exemplo de payloads (simplificado) POST /mfa/totp/enroll/start Response { "otpauth_uri": "otpauth://totp/MinhaApp:email?secret=...&issuer=MinhaApp", "enroll_id": "..." } POST /mfa/totp/enroll/confirm { "enroll_id": "...", "code": "123456" }Boas práticas: exija reautenticação recente (ou step-up) para iniciar enrollment; gere e entregue códigos de recuperação ao final.
Cadastro WebAuthn (passkey) (enrollment)
Objetivo: registrar uma credencial WebAuthn para o usuário.
Iniciar: servidor cria um
challengeWebAuthn (bytes aleatórios) e retorna opções de criação (RP ID, user handle, etc.).Criar credencial no cliente: o navegador/app chama a API WebAuthn e retorna a attestation.
Finalizar: servidor valida a attestation, extrai
credential_idepublic_key, e salva.
// Roteiro de endpoints (alto nível) POST /mfa/webauthn/register/options Response { publicKey: { challenge: "...", rp: {...}, user: {...}, pubKeyCredParams: [...] } } POST /mfa/webauthn/register/verify { "attestation": { ... } }Boas práticas: valide origin/RP ID corretamente; use bibliotecas maduras para WebAuthn; registre múltiplas credenciais (ex.: celular e notebook) para reduzir lockout.
Roteiro de endpoints e estados para um fluxo MFA simples e robusto
A seguir, um roteiro de endpoints que separa claramente: autenticação primária, desafio MFA, verificação MFA e emissão do estado autenticado final. A ideia funciona tanto para sessões quanto para tokens, desde que você só “finalize” após MFA.
1) Login primário
| Endpoint | Descrição | Resposta |
|---|---|---|
POST /auth/login | Valida credenciais primárias | Se não precisa MFA: emite autenticação final. Se precisa MFA: retorna mfa_required + challenge_id. |
// Response quando MFA é necessário { "mfa_required": true, "challenge_id": "c_123", "allowed_methods": ["webauthn","totp","recovery"], "expires_in": 300 }Regras: se o usuário tem dispositivo confiável válido, pode retornar autenticação final diretamente; caso contrário, crie mfa_challenge pendente.
2) Iniciar desafio por método (quando aplicável)
Alguns métodos precisam de “options”/envio antes da verificação.
| Endpoint | Quando usar | Observações |
|---|---|---|
POST /auth/mfa/webauthn/options | Para WebAuthn | Gera challenge WebAuthn vinculado ao challenge_id. |
POST /auth/mfa/sms/send | Para SMS | Envia código; aplicar rate limit forte e antifraude. |
// Exemplo: POST /auth/mfa/webauthn/options { "challenge_id": "c_123" } // Response { publicKey: { challenge: "...", allowCredentials: [...] } }3) Verificar MFA
| Endpoint | Descrição | Entrada |
|---|---|---|
POST /auth/mfa/verify | Valida o segundo fator e finaliza o login | Inclui challenge_id + prova do método |
Exemplos de payload por método:
// TOTP { "challenge_id": "c_123", "method": "totp", "code": "123456", "trust_device": true } // Recovery code { "challenge_id": "c_123", "method": "recovery", "code": "RC-ABCD-EFGH" } // WebAuthn { "challenge_id": "c_123", "method": "webauthn", "assertion": { ... } }Regras de verificação:
- Verifique se
challenge_idexiste, estápending, não expirou e pertence ao usuário correto. - Aplique rate limit e incremente
attempt_countem falhas. - Em sucesso: marque desafio como
verified, registre auditoria e emita autenticação final. - Se
trust_device=true: crietrusted_devicee emita cookie/token persistente do dispositivo (hash no servidor).
4) Gerenciar dispositivos confiáveis
| Endpoint | Descrição |
|---|---|
GET /mfa/trusted-devices | Lista dispositivos confiáveis do usuário |
DELETE /mfa/trusted-devices/:device_id | Revoga um dispositivo |
Boas práticas: exija step-up (MFA) para revogar/adicionar dispositivos confiáveis, pois isso afeta diretamente a postura de segurança.
5) Gerenciar métodos e códigos de recuperação
| Endpoint | Descrição |
|---|---|
POST /mfa/recovery-codes/regenerate | Gera novos códigos (revoga os antigos) |
POST /mfa/methods/disable | Desabilita MFA (exigir step-up forte) |
Regras: regenerar códigos invalida todos os anteriores; desabilitar MFA deve exigir verificação recente e registrar auditoria.
Checklist de validações críticas no back-end
- Nunca emita autenticação final antes do MFA ser concluído quando
mfa_enablede dispositivo não confiável. - Desafios com TTL curto, estado persistido e limite de tentativas.
- Rate limit por usuário/IP/desafio, com bloqueio progressivo.
- WebAuthn preferencial para reduzir phishing; evite SMS como padrão.
- TOTP secret cifrado em repouso, com chaves fora do banco e auditoria de acesso.
- Recovery codes armazenados como hash e de uso único.
- Auditoria completa de eventos de MFA, sem dados sensíveis em logs.