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_KEYdo .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/refreshgera novo par access+refresh - Token antigo é revogado imediatamente
- Previne replay attacks
Logout¶
/auth/logoutmarca 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)