Como implementar autenticação multifator (MFA) em aplicações web

1. Fundamentos da Autenticação Multifator (MFA)

A autenticação multifator (MFA) é um mecanismo de segurança que exige que o usuário apresente duas ou mais evidências (fatores) distintas para provar sua identidade antes de obter acesso a um sistema. Diferentemente da autenticação de fator único (apenas senha), a MFA reduz drasticamente o risco de comprometimento, pois um invasor precisaria violar múltiplas camadas de segurança simultaneamente.

Os fatores de autenticação são classificados em:
- Conhecimento: algo que o usuário sabe (senha, PIN)
- Posse: algo que o usuário possui (smartphone, token físico)
- Inerência: algo que o usuário é (impressão digital, reconhecimento facial)
- Localização: algo onde o usuário está (geolocalização, rede confiável)

É importante distinguir 2FA (autenticação de dois fatores) de MFA (autenticação multifator). 2FA é um subconjunto da MFA que utiliza exatamente dois fatores. MFA pode envolver dois ou mais fatores. Já a autenticação adaptativa (ou baseada em risco) ajusta dinamicamente os requisitos de fatores com base no contexto da tentativa de login, como dispositivo, localização e horário.

2. Escolhendo o método de MFA adequado para sua aplicação

TOTP (Time-based One-Time Password)

O TOTP é o método mais comum e recomendado para aplicações web. Utiliza um segredo compartilhado entre servidor e aplicativo autenticador (Google Authenticator, Authy, Microsoft Authenticator) para gerar códigos de 6 dígitos que expiram a cada 30 segundos.

// Exemplo de geração de segredo TOTP (pseudo-código)
secreto = gerarBytesAleatorios(20)
uri = "otpauth://totp/App:usuario@email.com?secret=" + base32Encode(secreto) + "&issuer=App"
qrcode = gerarQRCode(uri)
// Exibe QR code para o usuário escanear

SMS e e-mail como canais de OTP

Embora convenientes, SMS e e-mail apresentam riscos significativos de interceptação (ataques SIM swap, phishing de e-mail). Devem ser usados apenas como fallback ou quando TOTP não é viável.

// Exemplo de envio de OTP por SMS
funcao enviarOTPporSMS(telefone, codigo):
    // Utiliza API de gateway SMS (Twilio, Vonage)
    resposta = apiSMS.enviar(telefone, "Seu código de verificação: " + codigo)
    se resposta.sucesso:
        armazenarHash(codigo, expiracao=5min)
        retornar "Código enviado"
    senao:
        retornar "Erro no envio"

Chaves de segurança FIDO2/WebAuthn e biometria

FIDO2/WebAuthn é o padrão mais seguro, utilizando criptografia de chave pública. O dispositivo do usuário gera um par de chaves: a chave privada nunca sai do dispositivo, e a chave pública é registrada no servidor.

// Exemplo de registro WebAuthn (pseudo-código)
// 1. Servidor envia desafio aleatório
desafio = gerarBytesAleatorios(32)

// 2. Navegador cria credencial
credencial = await navigator.credentials.create({
    publicKey: {
        challenge: desafio,
        rp: { name: "App" },
        user: { id: userId, name: "usuario@email.com" },
        pubKeyCredParams: [{ alg: -7, type: "public-key" }]
    }
})

// 3. Servidor armazena chave pública e ID da credencial
armazenarChavePublica(userId, credencial.id, credencial.response.publicKey)

3. Fluxo de implementação do registro de MFA

O registro de MFA deve ocorrer após a autenticação inicial do usuário. O fluxo típico é:

  1. Verificação inicial: O usuário já está autenticado com senha. Solicite confirmação de identidade (ex: reenvio de senha ou e-mail de verificação).

  2. Geração do segredo: Crie um segredo compartilhado criptograficamente seguro.

// Geração de segredo TOTP em Node.js (biblioteca speakeasy)
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

async function gerarSegredoMFA(usuario) {
    const segredo = speakeasy.generateSecret({
        name: `App:${usuario.email}`,
        length: 20
    });

    // Gera QR code
    const qrCodeDataURL = await QRCode.toDataURL(segredo.otpauth_url);

    // Armazena segredo temporariamente (hash)
    armazenarSegredoTemporario(usuario.id, segredo.base32);

    return { segredo: segredo.base32, qrCode: qrCodeDataURL };
}
  1. Validação do primeiro token: Solicite que o usuário escaneie o QR code e insira o código gerado pelo aplicativo autenticador. Valide o token antes de finalizar o registro.
// Validação do token TOTP
function validarToken(segredoBase32, token) {
    const valido = speakeasy.totp.verify({
        secret: segredoBase32,
        encoding: 'base32',
        token: token,
        window: 1 // permite ±30 segundos de tolerância
    });
    return valido;
}
  1. Armazenamento seguro: Após validação bem-sucedida, armazene o segredo no banco de dados de forma criptografada.
// Armazenamento seguro do segredo
funcao armazenarSegredo(userId, segredoBase32):
    segredoCriptografado = criptografar(segredoBase32, chaveMestre)
    banco.executar(
        "INSERT INTO mfa_secrets (user_id, secret, created_at) VALUES (?, ?, NOW())",
        [userId, segredoCriptografado]
    )

4. Fluxo de autenticação com MFA (login em duas etapas)

O fluxo de login com MFA segue duas etapas distintas:

Primeira etapa: validação de credenciais tradicionais

// Etapa 1: Validação de senha
funcao primeiraEtapaLogin(email, senha):
    usuario = banco.buscarUsuarioPorEmail(email)
    se usuario == null:
        retornar "Credenciais inválidas"

    se verificarSenha(senha, usuario.hashSenha) == falso:
        registrarTentativaFalha(usuario.id)
        retornar "Credenciais inválidas"

    // Gera token temporário para segunda etapa
    tokenEtapa2 = gerarTokenTemporario(usuario.id, expiracao=5min)
    retornar { status: "mfa_required", token: tokenEtapa2 }

Segunda etapa: solicitação e verificação do segundo fator

// Etapa 2: Verificação MFA
funcao segundaEtapaLogin(tokenEtapa2, codigoMFA):
    sessao = validarTokenTemporario(tokenEtapa2)
    se sessao == null:
        retornar "Sessão expirada"

    segredo = banco.buscarSegredoMFA(sessao.userId)
    se validarToken(segredo, codigoMFA) == falso:
        registrarTentativaFalha(sessao.userId)
        se contarTentativasFalhas(sessao.userId, 5min) >= 3:
            bloquearUsuarioTemporariamente(sessao.userId, 15min)
            retornar "Muitas tentativas. Tente novamente em 15 minutos."
        retornar "Código inválido"

    // Autenticação completa
    tokenSessao = gerarTokenSessao(sessao.userId)
    retornar { status: "autenticado", token: tokenSessao }

Tratamento de falhas

Implemente limites de tentativas (ex: 3 falhas em 5 minutos), bloqueio temporário progressivo e notificações ao usuário sobre tentativas suspeitas.

5. Gerenciamento de dispositivos confiáveis e sessões

Implementação de "lembrar este dispositivo"

// Criação de token de confiança
funcao criarTokenConfianca(userId, dispositivoInfo):
    tokenConfianca = gerarTokenAleatorio(64)
    hashToken = hashSHA256(tokenConfianca)

    banco.executar(
        "INSERT INTO trusted_devices (user_id, token_hash, device_info, expires_at) VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY))",
        [userId, hashToken, dispositivoInfo]
    )

    // Cookie seguro
    definirCookie("trust_token", tokenConfianca, {
        httpOnly: true,
        secure: true,
        sameSite: "strict",
        maxAge: 30 * 24 * 60 * 60 // 30 dias em segundos
    })

Políticas de expiração e revogação

Permita que o usuário visualize e revogue dispositivos confiáveis em suas configurações de segurança.

6. Recuperação de acesso e códigos de backup

Geração de códigos de recuperação

Durante o registro de MFA, gere 8-10 códigos de uso único (one-time recovery codes) e exiba-os ao usuário.

// Geração de códigos de recuperação
funcao gerarCodigosRecuperacao(userId):
    codigos = []
    para i de 1 ate 10:
        codigo = gerarCodigoAlfanumerico(12)
        hashCodigo = hashSHA256(codigo)
        banco.executar(
            "INSERT INTO recovery_codes (user_id, code_hash, used) VALUES (?, ?, false)",
            [userId, hashCodigo]
        )
        codigos.push(codigo)
    retornar codigos

Fluxo de redefinição de MFA

Caso o usuário perca o acesso ao segundo fator e aos códigos de backup, implemente um fluxo de redefinição via e-mail verificado com período de carência (ex: 24 horas) e notificações.

7. Boas práticas de segurança e considerações finais

  • Proteção contra força bruta: Implemente rate limiting por IP e por usuário em todas as etapas de MFA.
  • MFA bombing: Limite o número de solicitações de código por SMS/e-mail por minuto para evitar ataques de fadiga.
  • Logs de auditoria: Registre todos os eventos de MFA (registro, login bem-sucedido, falha, redefinição) com timestamp, IP e user-agent.
  • Comunicação clara: Explique ao usuário por que a MFA é necessária e forneça instruções claras durante o registro e uso.
  • Testes de usabilidade: Realize testes com usuários reais para garantir que o fluxo não seja frustrante ou confuso.

Implementar MFA corretamente é um investimento essencial na segurança da sua aplicação. Comece com TOTP, adicione fallback com códigos de recuperação e, progressivamente, incorpore métodos mais avançados como WebAuthn conforme sua base de usuários cresce.

Referências