Tokens & Events
Por que PASETO em vez de JWT, e como o outbox garante que license-manager e key-service concordem sobre o estado do cliente
Ainda não implementado — design alvo, ver overview.
Por que PASETO em vez de JWT
JWT não é um formato de token errado por natureza — é um formato flexível demais de um jeito que gera vulnerabilidade recorrente na prática, e as duas mais comuns compartilham a mesma raiz: o cabeçalho do JWT declara qual algoritmo foi usado para assinar, e é o verificador quem decide se confia nesse campo.
alg: none— o spec original do JWT permite um token “sem assinatura”, pensado para casos onde a integridade já é garantida por outro canal. Implementações de verificação que não recusam esse valor explicitamente aceitam qualquer payload que declarealg: nonecomo válido — um invasor monta o payload que quiser e não precisa assinar nada.- Confusão de algoritmo (RS256 → HS256) — um sistema que emite tokens assinados com chave assimétrica (RS256, verificados com a chave pública) pode ser enganado a aceitar um token forjado se o verificador também aceitar HS256 (chave simétrica) e usar a mesma string como chave nos dois casos: o invasor assina com HS256 usando a chave pública (que é, por definição, conhecida) como segredo simétrico, e o verificador — se configurado para aceitar os dois algoritmos e não fixar qual esperar — verifica com sucesso.
As duas falhas têm a mesma causa: o formato deixa “qual algoritmo confiar” como decisão de runtime do verificador, em vez de decisão fixada na emissão. PASETO (Platform-Agnostic Security Tokens) resolve isso eliminando a escolha: cada versão do formato (v4, aqui) e cada “purpose” (public — assinado, verificável por qualquer um com a chave pública; ou local — cifrado, só decifrável por quem tem a chave) usa um algoritmo fixo e não negociável. Não existe campo de cabeçalho dizendo “confie neste algoritmo” para um atacante manipular, porque não há escolha de algoritmo para manipular.
v4.public — a variante planejada aqui — usa Ed25519 para assinatura. O payload carrega os fatos que o software precisa para se autofiscalizar (tenant, status, entitlements materializados, limites, expiração): o software nunca decide “o que ele pode fazer” consultando o catálogo de novo, decide olhando só para o que veio no token daquela ativação. O endpoint de chaves públicas é próprio do key-service, formato customizado — não JWKS, porque JWKS é o formato de descoberta de chave do ecossistema JWT, sem equivalente padronizado em PASETO.
Revogação: push, não polling
Se o software tivesse que checar periodicamente “meu certificado ainda é válido?” contra uma lista de revogados, cada verificação seria uma janela de atraso entre uma licença ser revogada de fato e o software perceber — e cada polling é tráfego constante mesmo quando nada muda. O design planejado inverte para push: o key-service notifica o software ativamente quando revoga, em vez do software ficar perguntando. O software só reage quando há algo para reagir.
Outbox: o problema de dois sistemas de registro
license-manager (fonte da verdade de negócio) e key-service (autônomo depois da ativação) são dois bancos de dados separados. Quando um cliente é criado ou um produto é vinculado no license-manager, o key-service precisa saber — mas gravar no próprio banco e notificar o key-service não podem ser a mesma operação atômica: são dois sistemas diferentes, sem transação distribuída entre eles. Se o license-manager gravasse a mudança e depois chamasse o key-service via HTTP direto, um crash exatamente entre os dois passos deixaria o license-manager com o dado novo e o key-service sem saber — inconsistência silenciosa, sem nenhum erro visível na hora.
O padrão outbox resolve isso trocando “grava e chama” por “grava os dois, na mesma transação, no mesmo banco”: a mudança de negócio e um registro do evento correspondente (processed_events ou equivalente) são gravados juntos, atomicamente, porque estão no mesmo COMMIT. Um processo separado lê a tabela de eventos e entrega ao key-service depois, de forma assíncrona — se esse processo falhar no meio do caminho, o evento continua na tabela, disponível para tentar de novo. O commit da mudança de negócio nunca fica dependente de um serviço externo responder.
Entregar duas vezes o mesmo evento (o processo de entrega reenvia depois de uma falha que na verdade já tinha ido) é tratado com deduplicação: ON CONFLICT na chave do evento, mais controle de sequence na projeção do lado do key-service, então processar o mesmo evento de criação de cliente ou vínculo de produto duas vezes não duplica o efeito.
Reconciliação cobre o caso que o outbox sozinho não cobre: um dado editado direto no banco do license-manager, fora do fluxo que gera evento (uma correção manual, por exemplo). Sem uma rotina que periodicamente compara o estado dos dois lados e corrige divergência, uma licença desse tipo de edição ficaria com dado desatualizado no key-service indefinidamente — o outbox só garante entrega do que gerou evento, não corrige o que nunca gerou um.