Como implementar autenticação passwordless em aplicações web

1. Fundamentos da autenticação passwordless

A autenticação passwordless é um método de verificação de identidade que elimina o uso de senhas tradicionais. Em vez de armazenar hashes de senhas no servidor, o sistema utiliza tokens temporários ou credenciais criptográficas para autenticar usuários. Esse modelo reduz significativamente ataques de phishing, roubo de credenciais e a fadiga de senhas entre usuários.

Os principais modelos incluem:

  • Magic links: links únicos enviados por email que autenticam o usuário ao serem clicados
  • Códigos OTP: códigos numéricos de uso único enviados por SMS ou email
  • WebAuthn/FIDO2: autenticação baseada em chave pública/privada usando biometria ou chaves de segurança
  • Chaves de segurança físicas: dispositivos como YubiKey que geram assinaturas criptográficas

2. Projetando o fluxo de autenticação sem senha

O fluxo básico segue estas etapas:

  1. Usuário informa seu identificador (email ou telefone)
  2. Servidor gera um token efêmero e o envia ao identificador
  3. Usuário apresenta o token recebido
  4. Servidor valida o token e cria uma sessão autenticada

Após a validação, utilizamos JWT (JSON Web Tokens) para gerenciar sessões:

// Estrutura do JWT de acesso
{
  "sub": "user_12345",
  "email": "usuario@exemplo.com",
  "iat": 1700000000,
  "exp": 1700003600,
  "type": "access"
}

// Estrutura do JWT de refresh
{
  "sub": "user_12345",
  "iat": 1700000000,
  "exp": 1700086400,
  "type": "refresh"
}

O tratamento de erros inclui timeouts configuráveis (geralmente 5-15 minutos para tokens), limites de tentativas (máximo 3-5 por período) e fallback para reenvio após 30 segundos.

Geração de tokens seguros

// Node.js - Geração de token criptograficamente seguro
const crypto = require('crypto');

function generateSecureToken() {
  return crypto.randomBytes(32).toString('hex');
}

// Python - Alternativa com secrets
import secrets

def generate_otp():
    return ''.join(str(secrets.randbelow(10)) for _ in range(6))

Envio seguro

// Exemplo de envio por email usando SendGrid
POST https://api.sendgrid.com/v3/mail/send
Headers:
  Authorization: Bearer SG_API_KEY
  Content-Type: application/json

Body:
{
  "personalizations": [{"to": [{"email": "usuario@exemplo.com"}]}],
  "from": {"email": "noreply@suaapp.com"},
  "subject": "Seu link de acesso",
  "content": [{
    "type": "text/html",
    "value": "<a href='https://suaapp.com/auth/verify?token=TOKEN_AQUI'>Clique para acessar</a>"
  }]
}

Armazenamento e verificação

// Armazenamento em Redis com TTL
SETEX token:abc123 300 user_12345
// Token expira em 5 minutos

// Verificação
GET token:abc123
// Retorna user_12345 se válido, nil se expirado

4. Integração com WebAuthn e FIDO2

Registro de credencial (attestation)

// Servidor gera challenge aleatório
{
  "challenge": "base64url_encoded_random_bytes",
  "rp": {"name": "Minha Aplicação", "id": "suaapp.com"},
  "user": {
    "id": "base64url_user_id",
    "name": "usuario@exemplo.com",
    "displayName": "Usuário Exemplo"
  },
  "pubKeyCredParams": [{"type": "public-key", "alg": -7}],
  "timeout": 60000
}

Autenticação (assertion)

// Servidor envia challenge para login
{
  "challenge": "base64url_encoded_random_bytes",
  "allowCredentials": [{
    "id": "base64url_credential_id",
    "type": "public-key"
  }],
  "timeout": 60000
}

Armazenamento de chaves públicas

// Tabela de credenciais no banco de dados
{
  "user_id": "user_12345",
  "credential_id": "base64url_encoded",
  "public_key": "base64url_encoded",
  "counter": 0,
  "device_name": "iPhone 15"
}

5. Segurança e mitigação de ataques

Proteção contra replay e CSRF

// Geração de nonce para WebAuthn
let challenge = crypto.randomBytes(32);
sessionStorage.setItem('auth_challenge', challenge.toString('base64'));

// Token anti-CSRF em formulários
<input type="hidden" name="_csrf" value="csrf_token_aqui">

Rate limiting

// Limite por IP (5 tentativas por minuto)
RATE_LIMIT: 5
WINDOW: 60 segundos
REDIS_KEY: rate_limit:192.168.1.1:auth

// Limite por identificador (3 tentativas por 15 minutos)
RATE_LIMIT: 3
WINDOW: 900 segundos
REDIS_KEY: rate_limit:email:usuario@exemplo.com

Prevenção de enumeração

// Resposta genérica para sucesso e falha
HTTP 200 OK
{
  "message": "Se o email existir, você receberá um link de acesso"
}

// Delay uniforme de 500ms antes de responder
await delay(500);

6. Experiência do usuário e boas práticas

Design de interface simplificado

<!-- Formulário de login passwordless -->
<form id="loginForm">
  <label for="email">Email</label>
  <input type="email" id="email" required 
         placeholder="seu@email.com"
         autocomplete="email">

  <button type="submit" id="submitBtn">
    Enviar link de acesso
  </button>

  <div id="loading" style="display:none">
    Enviando link... 
    <div class="spinner"></div>
  </div>

  <div id="success" style="display:none">
    Link enviado! Verifique seu email.
  </div>
</form>

Suporte a múltiplos dispositivos

// Sincronização de credenciais via iCloud Keychain
// A credencial registrada em um dispositivo Apple
// fica disponível em todos os dispositivos do mesmo iCloud

// Gerenciadores de senha (1Password, Bitwarden)
// Detectam automaticamente campos de OTP e magic links

Fallback para senha tradicional

// Opção de fallback quando WebAuthn não está disponível
if (!window.PublicKeyCredential) {
  mostrarOpcaoOTP();
  // ou redirecionar para login com senha
}

7. Testes e implantação em produção

Testes unitários

// Teste de geração de token
describe('Token Generation', () => {
  it('deve gerar token com 64 caracteres hex', () => {
    const token = generateSecureToken();
    assert.equal(token.length, 64);
    assert.match(token, /^[a-f0-9]+$/);
  });

  it('deve expirar após 300 segundos', async () => {
    await storeToken('test_token', 'user_1', 300);
    await delay(301000);
    const result = await verifyToken('test_token');
    assert.isNull(result);
  });
});

Monitoramento e logging

// Estrutura de log para tentativas de login
{
  "timestamp": "2024-01-01T00:00:00Z",
  "event": "login_attempt",
  "identifier": "usuario@exemplo.com",
  "method": "magic_link",
  "success": true,
  "ip": "192.168.1.1",
  "user_agent": "Mozilla/5.0..."
}

// Alertas para anomalias
if (loginAttempts > 100 em 1 minuto) {
  alertarEquipeSeguranca();
  bloquearIP(ip);
}

Considerações de desempenho

// Cache Redis para tokens
// Configuração de cluster Redis para alta disponibilidade
// TTL máximo de 15 minutos para tokens de autenticação

// Balanceamento de carga
// Sessões podem ser armazenadas em Redis compartilhado
// CDN para assets estáticos (HTML, CSS, JS)

A implementação de autenticação passwordless requer atenção cuidadosa à segurança, experiência do usuário e escalabilidade. Comece com magic links ou OTP para sua base de usuários e evolua gradualmente para WebAuthn conforme a maturidade do sistema.

Referências