Pare de usar JWT
(gist.github.com/samsch)- JWT não é adequado para manter usuários autenticados, e para esse objetivo uma sessão com cookie comum é mais apropriada
- A especificação do JWT pressupõe tokens de curta duração, geralmente de cerca de 5 minutos ou menos, enquanto sessões precisam de uma duração maior
- É difícil viabilizar uma autenticação sem estado de forma segura, e para lidar com tokens com segurança acaba sendo necessário algum tipo de armazenamento de estado
- Um JWT que carrega apenas um token de sessão simples é menos eficiente e menos flexível do que um cookie de sessão comum, e credenciais de autenticação não devem ser armazenadas em localStorage nem em sessionStorage
- Quando for realmente necessário um token assinado de curta duração, PASETO, projetado com foco em segurança, é uma escolha melhor, mas não deve ser usado para sessões
Resumo essencial
- JWT não deve ser usado para manter usuários autenticados; para esse objetivo, a ferramenta melhor é uma sessão com cookie comum
- JWT não foi projetado para esse propósito e não é seguro para isso; para manter sessões de login, uma sessão com cookie tradicional é mais adequada
- Em tema relacionado, credenciais de autenticação, incluindo tokens JWT, não devem ser armazenadas em localStorage nem em sessionStorage
- Há apresentações sobre JWT que podem ser úteis, mas outros tópicos, como proteção contra CSRF, costumam ser tratados só de forma breve, então vale aprender isso separadamente em outras fontes
- Mesmo os casos de uso “válidos” de JWT citados no final do vídeo podem ser resolvidos com ferramentas melhores e mais seguras, especificamente o PASETO
Por que evitar JWT
- A especificação do JWT foi projetada apenas para tokens de vida útil muito curta, de cerca de 5 minutos ou menos, enquanto sessões precisam durar mais que isso
- Autenticação sem estado de forma segura não é viável, e algum estado sempre será necessário para lidar com tokens com segurança
- Se você precisa de um armazenamento de dados, é melhor guardar todos os dados do que lidar apenas com parte do estado do token
- O problema é tratado com mais detalhes em http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
- Na prática, existem aplicações que usam JWT dessa forma, mas essas aplicações são defeituosas, então não vale repetir o mesmo erro
- Um JWT que armazena apenas um token de sessão simples é menos eficiente e menos flexível do que um cookie de sessão comum, sem oferecer vantagens adicionais
- A própria especificação do JWT não é confiável aos olhos de especialistas em segurança, então deve ser evitada em usos ligados a segurança e autenticação de modo geral
- A especificação original permitia criar tokens falsos, e pode haver outros erros nela
- Os problemas da família de especificações JWT são discutidos com mais profundidade em JWT: The JSON Web Token standard is bad and everyone should avoid it
Contra-argumentos
- O argumento de que “o Google também usa JWT” não se aplica a sessões de usuário no navegador
- O Google não usa JWT para sessões de usuário no navegador; usa sessões com cookie comuns
- O JWT é usado apenas como meio de transporte de Single Sign On para transferir a sessão de login de um servidor ou host para outro
- Esse modo de uso entra no conjunto de casos de uso razoáveis do JWT
- O Google tem recursos e especialistas em segurança para criar e manter uma implementação de JWT mais segura
- Na prática, o JWT do Google não é igual ao JWT usado em outros lugares
- O argumento de que “sem estado é melhor” não combina com os requisitos de uma autenticação segura
- Sem recursos enormes, não dá para operar uma autenticação realmente sem estado de forma segura
- Para uma discussão relacionada, veja Stateless is a lie
- O problema de “não saber configurar sessões” pode ser resolvido, na maioria dos casos, com a documentação e as implementações dos frameworks de servidor web
- Tecnologia de sessão não é algo particularmente novo, por isso não é tão comum ver textos explicando isso com frequência
- Só com a documentação da implementação de sessão já deve ser possível seguir o processo de configuração
- Quase todo framework de servidor web inclui alguma implementação de sessão e, mesmo quando não vem ativada por padrão, normalmente ela pode ser habilitada com facilidade
- Express e outros frameworks Node.js são uma espécie de exceção parcial por causa da alta modularidade e da natureza de propósito único
- No Express, basta usar o middleware
express-sessione um store connector compatível com o armazenamento escolhido - Recomenda-se usar
connect-session-knexcom Postgres, MySQL ou, se possível, SQLite
Tokens de curta duração
- Se você precisar de um token assinado de curta duração para algum uso específico, existe uma especificação melhor, projetada com foco em segurança: PASETO
- Mesmo usando PASETO, ele não deve ser usado para sessões
Como sessões funcionam
- Para aprender mais sobre como sessões funcionam, vale conferir o gist de joepie91
2 comentários
JWT é uma forma de reduzir a criptografia de tokens e as consultas ao banco de dados, não um conceito em oposição à autenticação por cookie. Se o JWT for armazenado em um cookie seguro, o risco de roubo é o mesmo do método legado de autenticação por cookie
Gerenciar uma lista de expiração para fazer o JWT expirar traz vantagens do ponto de vista de desempenho. Há uma diferença de custo entre consultar apenas as informações de expiração no Redis e consultar o banco de dados de todos os membros.
Dezenas de milhares de consultas baseadas em índice em 100 mil linhas de membros (método legado com cookie)
vs
Dezenas de milhares de consultas a 50 itens da lista de expiração no Redis (método de expiração imediata com JWT)
O JWT realmente tem vantagens. Só que, em ambientes pequenos, a diferença tende a ser menor.
Comentários do Hacker News
Faltou um detalhe importante: estamos falando de sessões de usuário baseadas em navegador
Em comunicação entre serviços, muitas vezes dá para usar JWT muito bem
Além disso, li parte do texto linkado e há, por exemplo, textos como https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba.... Se JWT fosse um padrão tão terrivelmente inseguro assim, bastaria divulgar como hackear o AssumeRoleWithWebIdentity do AWS STS, ou então não divulgar e sair instalando mineradores de criptomoeda nas contas AWS de produção de empresas da Fortune 500. Já que JWT é tão inseguro assim, avise quando conseguir /sarcasmo
A parte de assinatura e criptografia do JWT é complexa, e só agora as bibliotecas JWT mais comuns em geral começaram a se acertar; antes não era assim. Muitas bibliotecas aceitavam o algoritmo
"none"[1], e em alguns casos usavam chave pública como se fosse segredo compartilhado, permitindo que um atacante forjasse tokens [2]. Isso é justamente o resultado da complexidade que o texto linkado criticaJWT também pode não entregar as funcionalidades desejadas para sessões de usuário. Não dá para invalidá-lo sem manter uma lista de revogação em algum lugar. Mas, se você precisa conferir um identificador contra uma lista de revogação a cada requisição, então pode simplesmente usar um ID de sessão opaco e fazer uma consulta por requisição. Claro, é possível usar tokens de vida curta e renová-los continuamente, mas em aplicações comuns que já precisam manter estado, não há muito motivo para isso
Dito isso, concordo totalmente que tokens assinados podem ser úteis em sistemas distribuídos ou comunicação entre máquinas. Não devemos confundir os dois casos
[1] https://nvd.nist.gov/vuln/detail/cve-2022-23540
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-54150
Hoje, como as principais bibliotecas em várias linguagens adotaram padrões mais sensatos, eu diria que atualmente ficou de fato bem seguro
Se “é seguro quando configurado e usado corretamente” já significasse bom design, então o mesmo valeria para coisas como X.509
Em muitos casos, há alternativas melhores. Tokens de sessão padrão ou chaves de API são amplamente usados na maioria dos grandes sites e se encaixam quase perfeitamente na maioria dos usos
Não estou dizendo que esse padrão não tenha valor algum. O melhor dele é ser um padrão básico para trocar informações sem coisas como codificação ASN.1, e as ferramentas desse mundo ASN.1 parecem extremamente frágeis e cheias de bugs
Por exemplo, eu não sei como explorar SAML, mas sei que é um padrão terrível porque transforma todo o parser XML em superfície de ataque. Não sou pesquisador de segurança, então não sei como encontrar vulnerabilidades em um parser XML, mas dá para entender que uma grande superfície de ataque é algo ruim
JWT é inseguro, mesmo usando um esquema de assinatura confiável baseado em RSA/chave pública? Mesmo sem segredo compartilhado?
Também acho estranha a alegação de que JWT vive tempo demais. Basta limitar a vida útil do JWT e ter um modelo de renovação com a autoridade de autenticação. Mesmo usando sessão baseada em cookie, no fim das contas você está armazenando algo em algum lugar. Dá para fazer um JWT valer só por 5 a 15 minutos, e 15 minutos é parecido com o tempo de cache de vários sistemas de autorização, incluindo o Entra. Até tokens de 5 minutos funcionam bem no navegador se houver um sistema de renovação
Por fim, prefiro separar identidade/autenticação dos serviços de aplicação/API. Isso permite externalizar o contexto, e processar JWT em cada requisição é mais fácil de lidar do que um sistema compartilhado de cache/estado que pode falhar de forma intermitente. Tokens assinados permitem validar a assinatura contra uma autoridade conhecida
Fora isso, a assinatura é criptograficamente válida. Basta validar todos os JWTs sempre, com vida curta
Vale lembrar que os tokens OIDC são todos JWT
Comparando sessão com lista de revogação de JWT, também há um argumento a favor da lista de revogação de JWT. Como JWT tem um horário de expiração limitado, basta manter na lista de revogação apenas os tokens que ainda não expiraram
É provável que os JWT revogados sejam só uma fração dos JWT válidos em circulação, então por requisição você consulta apenas um conjunto de dados muito pequeno
Se usar sessão, a lista de sessões válidas provavelmente será várias ordens de magnitude maior do que a lista de revogação, e portanto o custo de consulta e armazenamento do estado também será maior
Além disso, o texto diz que JWT é stateless, mas normalmente não é bem assim. Em geral, você não valida só o JWT; também busca, a cada requisição, o objeto de identidade correspondente — ou seja, os detalhes do usuário — para confirmar que ele ainda está ativo e que tem permissão para realizar aquela ação. Dá para validar o campo
iatdo JWT usando uma lista de revogação por usuário ou algo comominimum_issued_at. Isso também permite o padrão de “sair de todos os dispositivos”: basta definir ominimum_issued_atdo usuário para$NOW, e todos os tokens anteriores ficam revogados. Não é necessária consulta a uma lista de revogação individualselectcom índice no banco de dados, retornando 0 ou 1 linha. Na maioria dos casos, isso não é algo com que se preocuparEste texto coloca a maior parte do “por quê” em links para outros posts de blog, e esses posts em geral parecem incomodados com o fato de que “não dá para invalidar tokens JWT individuais”
Sempre que implementei isso, a orientação geral foi verificar nonces invalidados em algum lugar, o que também resolve a segunda alegação daquele texto
A afirmação de que “especialistas em segurança não confiam na própria especificação JWT” parece exigir mais evidências do que um único post de blog. E esse post, no geral, parece culpar implementações ruins, mas problemas de implementação ruim existem em qualquer padrão
No geral, não sei o que eu esperava ao clicar num link aleatório de gist
Além disso, dá para usar JWTs de vida mais curta no navegador sem problema e fazer o agente renová-los por conta própria. Se você usa Azure Entra ou vários outros provedores, é assim que realmente funciona. Dá para manter o JWT relativamente curto, algo como 5 a 15 minutos, e ainda verificar se o
jtifoi revogadoJWT é muito útil para separar e reutilizar a autoridade de acesso do sistema de aplicação/API. Você está deslocando a superfície de ataque, mas de um jeito confiável. O mundo inteiro usa chaves públicas em vários lugares, inclusive no SSH. Eu não usaria segredo compartilhado nem token de longa duração, mas tokens assinados com chave pública, de curta duração e vindos de uma origem validada e conhecida, em geral estão ok
Na prática, o que muitas vezes realmente dá problema são as chaves de API. Tive de implementar isso agora há pouco e, no meu caso, fiz a chave de API parecer um token Bearer também, com um prefixo curto
sak., seguido da parte de identidade (bytes UUID em base64url) e depois o valor secreto (bytes em base64url). No banco de dados, armazeno o UUID e um salt+hash em nível de passphrase gerado a partir do valor secreto. Assim, a chave de API gerada precisa ser tratada como segredo, e no banco ela fica armazenada apenas de forma unidirecional, de modo que um comprometimento do banco não vira automaticamente um comprometimento de autenticaçãoAinda assim, é muito mais provável haver vazamento de chave de API do que surgir um problema numa solução JWT bem implementada
Vi este texto por acaso e, como trabalhei muito com esse tema no passado, achei interessante que ele esteja voltando à tona agora. Mas, quando cliquei, o autor estava linkando parte do meu material. Que viagem no tempo
Enfim, pessoas muito mais inteligentes do que eu vêm tratando esse tema amplamente há anos, mas, em 2026, eu ainda acho que JWT é a ferramenta errada para autenticação web. Para uso entre serviços, tudo bem, mas, se houver escolha, é melhor simplesmente usar PASETO. Resolve muitos problemas
https://www.paseto.io/
Neste momento estou conectando RabbitMQ a um site para push de notificações. Estou usando autenticação JWT para controlar o que o cliente pode ler e de onde, com vida curta e renovação periódica dos tokens
Não conheço outra configuração que chegue perto da facilidade dessa. Basta adicionar um endpoint que forneça um token JWT para uma sessão válida, e pronto; também dá para ter permissões por usuário
Um dos textos linkados que supostamente explicam por que não se deve usar JWT é, na melhor das hipóteses, estranho
https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
O resumo é: “algumas bibliotecas tinham bugs”, e depois disso o texto recomenda trazer libsodium e fazer tudo na mão. É um conselho tão absurdo que fica difícil levar a sério. Todo software tem bugs. Na época do Heartbleed, a internet inteira entrou em pânico, mas ainda usamos TLS e OpenSSL
Também é a primeira vez que ouço que “a especificação JWT foi projetada especialmente para tokens de vida muito curta, algo em torno de 5 minutos ou menos”, e não consigo encontrar fundamento para isso. A RFC 7519 não faz essa afirmação
Em geral, JWT é usado como um cache de autenticação. Você recebe um token de autenticação de um serviço de autenticação, e esse token concede autorização para outros serviços
Há várias vantagens nisso, mas o principal é que os serviços subordinados não precisam interagir com o banco de dados de autenticação nem ter permissão para emitir tokens. Isso assumindo RS256, e não HMAC. Assim, se um serviço subordinado for comprometido, não é tão grave quanto comprometer um serviço que tem acesso ao banco de autenticação
Se houver dados sensíveis dentro do token, aí é preciso usar JWE, mas isso não é tão bom porque, cada vez que for usar, você terá de pedir a um serviço interno com a chave privada para descriptografar o token
A estrutura que eu costumo usar é
{"id": (uuid), "scopes": ["scope:read/write"]}Também é bem bom para SPA. Isso porque o servidor de site estático pode validar o JWE com uma chave pública antes de servir os recursos. O meu jeito é compilar o site estático no formato
/(scope)/path, e o serviço estático simplesmente não serve páginas às quais ele não pode acessar. Isso é muito útil quando você não quer expor ao usuário funcionalidades que estão no backend, como um painel administrativo, ou caminhos de serviços internos que poderiam ser atacadosO tempo de vida do JWT para “acesso ao backend” é de uns 5 minutos, e coisas como
/meficam em cache no localStorage, a menos que/refreshdiga explicitamente para descartar esse cache do localStorage. O handler de requisições da aplicação SPA detecta quando “precisa renovar” e atualiza o tokenAcho que grande parte dessa responsabilidade está nas bibliotecas de node/next e Python. Escrevo o backend em linguagens fortemente tipadas e sempre faço o frontend como páginas estáticas pré-compiladas. A configuração atual do frontend usa VITE, com landing pages pré-renderizadas e a aplicação como uma SPA comum
Mesmo levando tudo isso em conta, discordo fortemente desse gist como um todo. JWT pode ser tão seguro quanto você quiser
JWT é ok, e o título parece um pouco sensacionalista
Em vez disso, há temas bons para discutir: quando usar valores criptografados (simétricos ou assimétricos), valores aleatórios mas secretos, valores assinados (que podem ser lidos, mas não adulterados), onde guardar esses valores (memória, localStorage, cookies), como impedir que eles durem para sempre e se existe necessidade de descartá-los antes do vencimento natural