Ir para o conteúdo

MundiX - ADR-001: Autenticação OAuth2 com Refresh Token Rotation

Status: Aceito
Data: 2026-02-03
Task: MX-2026-000123
Autor: Agent-Architect


Contexto

O MundiX Orchestrator API precisa de autenticação para: - Proteger endpoints administrativos (CRUD de agentes, tasks) - Permitir frontend consumir API com segurança - Suportar múltiplas sessões (web, mobile futuro) - Implementar logout efetivo

Stack atual: FastAPI + PostgreSQL + Redis (disponível)


Decisão

Implementar OAuth2 Bearer Token com Refresh Token Rotation:

Access Tokens (JWT - Stateless)

  • Algoritmo: HS256 (symmetric)
  • TTL: 15 minutos
  • Payload: {"sub": user_id, "exp": timestamp, "type": "access"}
  • Key: Variável ORCHESTRATOR_API_SECRET_KEY do .env

Refresh Tokens (Opaque - Stateful)

  • Formato: UUID v4
  • TTL: 7 dias
  • Storage: PostgreSQL tabela refresh_tokens
  • Hash: SHA256 antes de salvar no DB

Rotação de Refresh Token

  • Cada /auth/refresh gera novo par access+refresh
  • Token antigo é revogado imediatamente
  • Previne replay attacks

Logout

  • /auth/logout marca refresh token como revogado
  • Access token expira naturalmente (15min)

Alternativas Consideradas

Alt 1: JWT para Access e Refresh

  • ❌ Não permite revogação granular
  • ❌ Logout não é efetivo
  • ✅ Totalmente stateless

Rejeitada: Requisito de logout efetivo obriga statefulness.

Alt 2: Redis para Refresh Tokens

  • ✅ Performance superior
  • ✅ TTL automático
  • ✅ Já temos Redis no stack
  • ❌ Adiciona complexidade (mais um ponto de falha)

Considerada para futuro: Migração de PostgreSQL → Redis é trivial se necessário.

Alt 3: PostgreSQL para Refresh Tokens (ESCOLHIDA)

  • ✅ Usa infra existente
  • ✅ Controle total de revogação
  • ✅ Queries simples
  • ❌ Latência maior que Redis (~10ms vs ~1ms)

Aceita para MVP: Simplicidade > performance prematura.


Consequências

Positivas

  • ✅ Segurança adequada (rotação + revogação)
  • ✅ Compatível com padrões OAuth2
  • ✅ Implementação direta (FastAPI tem suporte nativo)
  • ✅ Logout funcional
  • ✅ Multi-sessão (vários refresh tokens por user)

Negativas

  • ❌ Consulta DB a cada /auth/refresh (~10-50ms)
  • ❌ Cleanup manual de tokens expirados (cron job)

Neutras

  • 🔄 Migração futura PostgreSQL → Redis é transparente para API

Riscos e Mitigações

Risco Severidade Mitigação
Replay de refresh token High Rotação obrigatória + flag revoked
Brute force login Medium Rate limiting: 5 tentativas/min por IP
Token vazado (XSS) Medium TTL curto (15min) + HttpOnly cookies (frontend)
Secret key comprometida Critical Rotação da key invalida todos os JWTs. Usar env var segura.
DB indisponível Medium Fallback: retornar 503 (não gerar tokens inválidos)

Schema de Banco

Table: users

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  username VARCHAR(100) UNIQUE NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,  -- bcrypt
  is_active BOOLEAN DEFAULT true,
  is_admin BOOLEAN DEFAULT false,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);

Table: refresh_tokens

CREATE TABLE refresh_tokens (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  token_hash VARCHAR(64) NOT NULL,  -- SHA256
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
  revoked BOOLEAN DEFAULT false,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  revoked_at TIMESTAMP WITH TIME ZONE,
  UNIQUE(token_hash)
);

CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id);
CREATE INDEX idx_refresh_tokens_hash ON refresh_tokens(token_hash);
CREATE INDEX idx_refresh_tokens_expires ON refresh_tokens(expires_at);

Arquitetura de Componentes

┌───────────────────────────────────────────────┐
│ Client (Frontend / CLI / Telegram Bot)       │
└───────────────┬───────────────────────────────┘
┌───────────────────────────────────────────────┐
│ POST /auth/login                              │
│   Input: {username, password}                 │
│   Output: {access_token, refresh_token, ...} │
│                                               │
│ POST /auth/refresh                            │
│   Input: {refresh_token}                      │
│   Output: {access_token, refresh_token, ...} │
│                                               │
│ POST /auth/logout                             │
│   Input: {refresh_token}                      │
│   Output: {success: true}                     │
└───────────────┬───────────────────────────────┘
┌───────────────────────────────────────────────┐
│ Middleware: verify_jwt_token                  │
│   - Extrai Bearer token do header             │
│   - Valida JWT (signature + expiration)       │
│   - Injeta current_user no request context    │
│   - Endpoints protegidos: /agents, /tasks     │
└───────────────┬───────────────────────────────┘
┌───────────────────────────────────────────────┐
│ PostgreSQL: users + refresh_tokens            │
└───────────────────────────────────────────────┘

Implementação (Agent-Backend)

Ver subtask MX-2026-000123-B para detalhes.

Files a criar: - orchestrator/common/auth.py - Crypto (JWT, bcrypt, SHA256) - orchestrator/common/models.py - Adicionar User + RefreshToken - orchestrator/api/auth.py - Endpoints /auth/* - orchestrator/api/dependencies.py - Dependency get_current_user - orchestrator/api/main.py - Registrar router


Testes (Agent-QA)

Ver subtask MX-2026-000123-C.

Cobertura mínima: 85%


Security Review (Agent-Sec)

Ver subtask MX-2026-000123-D.

Checklist: - [ ] Secrets em env vars (não hardcoded) - [ ] Password hash com bcrypt (work factor >= 12) - [ ] JWT signature validation - [ ] Refresh token rotation - [ ] Rate limiting implementado - [ ] Logs não expõem tokens/senhas


Referências


Status: ✅ Arquitetura definida
Próxima etapa: MX-2026-000123-B (Backend implementation)