OAuth2 é fácil no quadro branco e brutal em produção

Este artigo defende que OAuth2 parece simples na teoria e costuma ser mal implementado em produção, e mostra onde as equipes de engenharia sênior realmente erram: armazenamento inseguro de tokens, rotação e revogação de refresh tokens quebradas, scopes que crescem sem controle e a falsa sensação de segurança de usar uma biblioteca padrão.

OAuth2 é fácil no quadro branco e brutal em produção featured image
17 de ago. de 20269 min de leitura
Atualizado em 2 de nov. de 2026

Você já implementou “Login com Google” mais vezes do que consegue contar. Já desenhou o authorization code flow do OAuth2 no quadro branco em pelo menos três entrevistas, com as setas de redirect e o cadeadinho do lado do token. Você sabe o que é um scope. Leu a RFC, ou pelo menos passou os olhos nela uma vez durante um sprint tranquilo. Por qualquer critério razoável, você entende OAuth2.

Aí você coloca em produção e, seis meses depois, chega na sua caixa de entrada um relatório de pentest com achados que dão vontade de sumir dentro do teclado.

Essa distância não tem nada a ver com OAuth2 ser complicado. A spec é quase ofensivamente simples quando você vê o diagrama: redirect, consentimento, code, troca pelo token, pronto. A distância existe porque OAuth2 é um framework, não uma receita. Ele deixa dezenas de decisões nas mãos de quem implementa, e a maioria das equipes toma essas decisões uma vez só, com prazo apertado, e nunca mais volta nelas. O resultado são sistemas em produção que passam em todo code review e mesmo assim vazam tokens, continuam confundindo quem o usuário é com o que ele tem permissão para fazer e continuam confiando em scopes que ninguém audita há dois anos.

Veja onde isso acontece de fato, e por que “usamos uma biblioteca padrão” não é o álibi que a maioria dos engenheiros acha que é.

OAuth2 nunca foi feito para responder “quem é este usuário”

Esta é a confusão que mais causa estrago lá na frente, porque no começo parece inofensiva. OAuth2 é um protocolo de autorização. Uma troca de token bem-sucedida prova que uma aplicação tem permissão para chamar uma API em nome de alguém. Ela não prova quem essa pessoa é. Em algum momento, um número enorme de equipes passou a tratar “o usuário completou o flow de OAuth2” como equivalente a “o usuário está autenticado”, e construiu a lógica de sessão em cima dessa suposição.

Funciona bem até parar de funcionar. O access token que você recebe é opaco ou, se for um JWT, pode até trazer um claim sub, e alguém da equipe decide que isso basta para identificar o usuário. Ninguém valida a audience do token. Ninguém confere se ele foi emitido mesmo para a sua aplicação e não para outro client que compartilha o mesmo authorization server. O OpenID Connect existe como uma camada sobre o OAuth2 justamente para fechar essa brecha, com um ID token próprio e claims de identidade em que você pode confiar de verdade. As equipes que pulam essa parte e improvisam a própria lógica de “identidade a partir do access token” são as que acabam com bugs de account takeover que nunca aparecem numa demo, só na revisão de um incidente.

Onde os access tokens vão para morrer

Armazenar tokens é a parte a que todo mundo chega uma hora e quase ninguém acerta na primeira vez. Web apps que guardam access tokens no localStorage porque é o caminho mais fácil durante o sprint, sem perceber que qualquer XSS bem-sucedido na página agora tem um jeito limpo de exfiltrar uma sessão ativa. Apps mobile que jogam tokens em shared preferences ou num arquivo plist sem nenhuma camada de criptografia, porque a biblioteca de OAuth2 cuidou do flow e ninguém pensou no que acontece depois que o token cai no disco. Middleware de logging que despeja os headers completos da requisição, Authorization incluído, num agregador com uma política de retenção de seis meses de que ninguém se lembra até uma revisão de segurança apontar.

Tem também uma versão desse erro que parece mais sofisticada e não é. Equipes que armazenam os tokens corretamente no client, mas depois repassam esses tokens, sem alteração, para cada microservice downstream como mecanismo de autenticação interna de fato. Um único serviço comprometido agora tem um token com scope para a sessão inteira do usuário, não só para a sua fatia do trabalho. Armazenar tokens não é só um problema do client. É um problema de arquitetura, e continua importando depois que o token sai do navegador ou do app.

Nada disso exige uma solução exótica. Access tokens de vida curta, cookies httpOnly onde a arquitetura permitir, armazenamento seguro nativo da plataforma no mobile e uma limpeza de logs que remove os headers de autenticação antes de qualquer coisa ser persistida. A solução é chata. É exatamente por isso que ela fica de lado: não aparece numa demo e não é a parte do OAuth2 que alguém coloca num slide.

A rotação de refresh tokens é a parte que ninguém termina de construir

Todo mundo implementa a troca do access token, porque sem ela não dá para lançar. Quase ninguém termina de implementar direito a rotação de refresh tokens, porque dá para lançar sem ela e os atalhos só aparecem quando algo dá errado. Rotação significa que, toda vez que um refresh token é usado, o antigo é invalidado e um novo é emitido. Se esse token invalidado aparecer de novo, é sinal de que outra pessoa tem uma cópia, e a família inteira de tokens deve ser derrubada na hora.

A maioria das implementações pula isso. Um refresh token vive trinta ou noventa dias, é reutilizado sem nenhum rastreamento e, se vazar, a exfiltração fica invisível até alguém notar uma atividade estranha semanas depois, se é que alguém nota. Mesmo as equipes que implementam rotação muitas vezes pulam a detecção de reuso, o que anula todo o sentido de rotacionar. E a revogação, a parte em que fazer logout, trocar a senha ou um admin marcar uma conta como comprometida de fato derruba os tokens correspondentes no servidor, é tratada como algo “bom de ter” até um incidente torná-la urgente. A essa altura, ela vira um hotfix sob pressão em vez de uma decisão de design tomada com calma e com antecedência.

O scope que você adicionou “temporariamente” há dois anos continua lá

Scopes existem para limitar o raio de impacto. Na prática, as equipes pedem scopes amplos logo no começo porque a integração funciona, dizem para si mesmas que vão restringir depois do lançamento, e o depois do lançamento nunca chega. Dois anos depois, uma integração de terceiros ainda tem acesso de leitura e escrita ao calendário inteiro de um usuário, porque restringir para “ler eventos deste calendário específico” teria significado trabalho extra num sprint que já estava cheio demais.

Aí as client credentials de OAuth2 dessa integração vazam, ou o terceiro sofre uma invasão, e o raio de impacto é o calendário inteiro em vez da fatia pequena de dados de que a integração realmente precisava. Least privilege não é um checkbox de compliance que você marca para passar numa revisão de segurança. É o único fator que decide se uma invasão vira um incidente contido ou um desastre completo, e acertar isso não custa nada além da disciplina de dizer não ao scope mais amplo quando o mais restrito resolve.

Uma biblioteca padrão de OAuth2 não vai te salvar de você mesmo

Este é o mito que mais causa estrago, porque soa completamente razoável. “Usamos Auth0, Okta, Passport, tanto faz, então estamos cobertos.” Uma boa biblioteca cuida corretamente da mecânica do protocolo. Ela monta as URLs de redirect certas, valida assinaturas, troca codes por tokens conforme a spec e te poupa de uma categoria de erros que era comum quando todo mundo escrevia o próprio client de OAuth2 na mão.

O que ela não faz é escolher os seus scopes por você. Ela não decide onde o token que ela te entrega vai ser armazenado. Ela não define a duração da sua sessão, a sua política de rotação nem o quanto a validação da sua redirect URI precisa ser rígida para bloquear um open redirect. Cada decisão de segurança real numa implementação de OAuth2 mora na configuração e no código da aplicação em volta da biblioteca, que é justamente a parte que a biblioteca não enxerga. Usar uma biblioteca de OAuth2 bem mantida elimina uma categoria inteira de erros. Ela não elimina os que a sua equipe está cometendo agora, e tratá-la como garantia de segurança é como as equipes param de procurar os erros que de fato cabe a elas corrigir.

O problema não é o OAuth2, é o descaso

Nada disso significa que OAuth2 é uma spec ruim, e muito menos que você deveria criar algo caseiro no lugar. Esse caminho é pior, sempre. O que isso significa é que a parte difícil do OAuth2 nunca foi o diagrama do flow que você desenhou numa entrevista. A parte difícil é o trabalho de manutenção sem glamour que quase ninguém quer assumir: revisar quais scopes ainda são necessários de verdade, rotacionar tokens do jeito que a spec realmente pretende e não do jeito mais rápido de lançar, decidir onde os tokens ficam antes que uma revisão de segurança force a pergunta.

OAuth2 em produção não é um protocolo que você implementa uma vez e esquece. É um conjunto de decisões que você precisa continuar tomando direito enquanto o sistema estiver no ar. A maioria das equipes para de tomá-las direito logo depois do lançamento, quando o diagrama ainda está fresco e a demo ainda funciona. Isso não é uma falha da spec. Essa é a vulnerabilidade de verdade, e ela está no seu codebase agora mesmo, quer alguém já tenha procurado por ela, quer não.

ESCRITO POR

Equipo de redacción de contenido de Howdy
Howdy Editorial Team
COMPARTILHAR