4 pontos por GN⁺ 18 시간 전 | 2 comentários | Compartilhar no WhatsApp
  • 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

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-session e um store connector compatível com o armazenamento escolhido
    • Recomenda-se usar connect-session-knex com 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

 
shj5508 9 시간 전
  1. 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

  2. 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

    • Essa é exatamente a conclusão razoável. Concordo que JWT é a ferramenta errada para sessões de usuário no navegador
      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 critica
      JWT 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
    • Antigamente, JWT tinha muitos problemas por causa de bibliotecas com padrões ruins por padrão. Alguns anos atrás, ataques de downgrade também eram bem comuns
      Hoje, como as principais bibliotecas em várias linguagens adotaram padrões mais sensatos, eu diria que atualmente ficou de fato bem seguro
    • JOSE, mesmo sendo seguro quando implementado corretamente, ainda pode causar problemas. Em muitos casos, a superfície de API relacionada é ruim
      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
    • Concordo com a primeira parte, mas o complemento é uma falácia lógica. Não é preciso conseguir hackear algo para dizer que aquilo é inseguro
      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
    • A linhagem de vulnerabilidades causadas por JWT em aplicações reais é longa
  • 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

    • Um JWT pode ser feito para expirar em 30 segundos, ou até em 1 segundo. Ao criar um JWT, é preciso definir a audience
      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 iat do JWT usando uma lista de revogação por usuário ou algo como minimum_issued_at. Isso também permite o padrão de “sair de todos os dispositivos”: basta definir o minimum_issued_at do usuário para $NOW, e todos os tokens anteriores ficam revogados. Não é necessária consulta a uma lista de revogação individual

    • No momento em que você precisa buscar o objeto do usuário, a principal vantagem do JWT desaparece, então pode simplesmente descartá-lo
    • Buscar dados de sessão é só um select com índice no banco de dados, retornando 0 ou 1 linha. Na maioria dos casos, isso não é algo com que se preocupar
    • Sessões também têm horário de expiração, e você pode configurá-lo como quiser
  • Este 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

    • Algumas implementações iniciais permitiam definir qualquer autoridade no cabeçalho e confiavam nela como se nada fosse, o que obviamente já estava errado desde o começo. Se você permitir apenas autoridades confiáveis ou “conhecidas”, muitas preocupações de contexto desaparecem
      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 jti foi revogado
      JWT é 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ção
      Ainda assim, é muito mais provável haver vazamento de chave de API do que surgir um problema numa solução JWT bem implementada
    • Não concordo 100% com a afirmação de que “não dá para invalidar tokens JWT individuais”. Para mim, verificar nonces invalidados em algum lugar durante a implementação é bom senso, e ainda me surpreendo toda vez que descubro de novo que as pessoas não fazem isso
  • 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

  • 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 atacados
    O tempo de vida do JWT para “acesso ao backend” é de uns 5 minutos, e coisas como /me ficam em cache no localStorage, a menos que /refresh diga explicitamente para descartar esse cache do localStorage. O handler de requisições da aplicação SPA detecta quando “precisa renovar” e atualiza o token
    Acho 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