Invalidação de tokens em sistemas distribuídos
Nesta página
Construir sistemas rápidos pode ser complicado, e a computação distribuída costuma ser o caminho escolhido para ganhar desempenho. Isso traz desafios próprios de segurança, em especial na autenticação e na autorização. Os JSON Web Tokens do OAuth 2 resolvem parte disso, mas trazem riscos na revogação.
Autenticação distribuída na SEEK
A SEEK conhece bem o modelo de computação distribuída. Quase tudo que você acessa é servido por dezenas de microsserviços escritos em linguagens diferentes e mantidos por times diferentes.
Sidecar - uma tarefa coesa acoplada à aplicação principal - normalmente colocada em seu próprio processo ou contêiner - que oferece uma interface homogênea aos serviços, independentemente da linguagem.
Na SEEK resolvemos isso colocando um sidecar ao lado das nossas aplicações. O sidecar valida os tokens, aceita ou rejeita as requisições de entrada e repassa o contexto do usuário para a aplicação.
Para atender às muitas linguagens que existem nas aplicações da SEEK, a interface homogênea escolhida é o HTTP. Ele tem implementação em quase todas as linguagens que usamos, e os headers são uma boa estratégia para acrescentar informação às requisições.
Outra forma de enxergar o Authentication Sidecar é como um proxy.
Um exemplo do padrão de projeto sidecar com dois conjuntos de tarefas operando num ambiente de nuvem com escala automática. Nesse cenário, você vê uma requisição bem-sucedida passando pelo sidecar até a tarefa da aplicação e uma requisição não autorizada sendo encerrada no sidecar. Aqui o Authentication Sidecar usa um JSON Web Key Set (JWKS) em cache para validar a assinatura dos tokens. Isso permite validar tokens de forma distribuída, sem precisar chamar o Identity Provider a cada requisição.
Esse modelo é rápido e escalável, mas traz um risco: um usuário bloqueado ainda consegue fazer requisições até o JSON Web Token (JWT) expirar. A especificação não recomenda um tempo de expiração, mas é prática comum que eles durem pouco, cerca de 15 minutos.
Revogação de tokens
Parte do padrão OAuth 2 é a RFC7009, a especificação de Token Revocation. Ela descreve o processo bem básico de revogar tokens.
A especificação define um endpoint que o cliente pode chamar para enviar o token que quer revogar. As validações seguintes desse token são rejeitadas, porque ele agora é considerado inválido.
Essa especificação só funciona com tokens opacos, não com JSON Web Tokens. Esse problema existe em muitas empresas, a SEEK inclusive. Nossa arquitetura distribuída torna extremamente difícil verificar tokens de forma eficiente contra uma fonte central. Por isso assinamos tokens com expiração curta e aceitamos o risco de que um usuário bloqueado ainda faça requisições até o token expirar.
Outro desafio é ter um jeito de identificar o token. Os JSON Web Tokens carregam toda a informação necessária para identificar o usuário. Eles são empacotados, assinados e enviados, não armazenados. Isso significa que é preciso adicionar uma claim a mais para identificar o token, e para isso existe o JWT ID.
Invalidação distribuída de tokens
Invalidar tokens num sistema distribuído pode ser complicado, mas dá para usar um bloom filter e reduzir bastante o custo de checar JWT IDs invalidados contra uma fonte central.
Os tokens costumam durar pouco, e em nenhum cenário esperaríamos bloquear 100.000.000 de tokens, que cabem tranquilamente em cerca de ~500MBs de memória na forma de um bloom filter, com chance de 1 em 999.925.224 de falso positivo.
Para resolver esse cenário, venho trabalhando no oauth-revokerd. A função desse serviço é manter uma lista de JWT IDs revogados e distribuir um bloom filter dessa lista.
O serviço traz um banco de dados em memória para guardar os tokens revogados no curto prazo, até o token expirar. A ideia geral é subir o serviço o mais rápido possível, com configuração mínima, oferecer uma API de gerenciamento para revogar tokens e uma API de consulta para verificar as revogações nos casos de falso positivo.
Nesse conceito, o sidecar é estendido para consumir o bloom filter.
Concluindo
Por enquanto é basicamente isso. Espero fazer mais com esse projeto no futuro e vou postar as novidades no meu site.