Você sabia que esse tal de JWT não é só um token bonito com três pontinhos separados por ponto?
Pois é... ele pode ser um JWS (assinado, mas ainda legível pra quem quiser fuçar) ou um JWE (criptografado de verdade, pra variar).
E olha só: Atualmente a RFC recomenda utilizar o algoritmo ES256, que usa ECDSA com curva P-256 e SHA-256. É, meu amigo, não é só sair assinando com HMACzinho hardcoded na mão.
Se você ainda tá protegendo suas APIs com um JWT no modo tutorial do foguete, para tudo. Lê o artigo e tenta não fazer besteira. E digo mais, eu conto nos dedos da mão esquerda quantos blogs ensinam JWT corretamente.
JSON Web Token (JWT)
JWT é a sigla para JSON Web Token, uma estrutura compacta, baseada em JSON, projetada para ser segura e fácil de trafegar via HTTP. Inclusive, é URL-safe, ou seja, pode passear pela URL sem quebrar nada (bom, quase nada... até alguém tentar enfiar o JWT inteiro no header).
Pense no JWT como uma espécie de classe abstrata: ele por si só não faz muita coisa, mas suas implementações sim. É aí que entram o JWS (token assinado) e o JWE (token criptografado). O JWT apenas define o formato; quem faz o trabalho sujo são esses dois.
O conteúdo de um JWT é composto por claims, que nada mais são do que pares chave/valor. Essas claims fornecem dados sobre o usuário ou o contexto da requisição, e são lidas tanto pelo client quanto pelas APIs envolvidas.
Onde ele é usado?
É amplamente utilizado para transferir dados entre sistemas por meio do protocolo HTTP. Seja entre um frontend SPA e uma API REST, ou até entre APIs diferentes. E sim, é tentador usar JWT pra tudo… mas nem sempre é a melhor ideia. (Fica o aviso.)
Estrutura e o Padrão JOSE
Embora o JWT seja um objeto JSON, sua segurança depende de outro conjunto de padrões chamado JOSE, Javascript Object Signing and Encryption. O JOSE define como assinar, criptografar e lidar com chaves e algoritmos. Ele é composto pelos seguintes componentes, cada um com sua respectiva RFC:
Acrônimo | Nome Completo | Descrição |
---|---|---|
JWS | JSON Web Signature (RFC 7515) | Define como assinar digitalmente um JWT. |
JWE | JSON Web Encryption (RFC 7516) | Define como criptografar um JWT. |
JWK | JSON Web Key (RFC 7517) | Especifica um formato JSON para representar chaves criptográficas. |
JWA | JSON Web Algorithms (RFC 7518) | Lista os algoritmos válidos para assinatura e criptografia. |
Em resumo, o JOSE é o cérebro por trás da operação, garantindo que o token não seja adulterado no caminho e que ninguém leia o que não deveria, a não ser, claro, que você use um algoritmo fraco ou publique sua chave privada no GitHub. Aí, nem JOSE salva.
JSON Web Signature (JWS)
Vamos direto ao ponto: o JWS é o formato mais usado com JWT. Tão usado que a gente chama o JWS de JWT. Se ele fosse sua namorada(o) você teria sérios problemas. Mas quem se importa com detalhe técnico quando tem PR pra fechar.
O que diabos é isso então?
Simples: quando você assina digitalmente um JWT, ele se transforma num JWS. Ou seja, o JWS é um JWT assinado, ponto. Não esconde o conteúdo, não embaralha, não criptografa... só garante que ninguém mexeu na estrutura. Pois ao mexer na estrutura, vai precisar recalcular a assinatura. E é ai que a gente pega o salafrario no pulo. É tipo um lacre de delivery: se rasgar, alguém fuçou.
Como funciona a tal assinatura?
A assinatura é gerada juntando o Header com o Payload (ambos em base64url), separando com um pontinho maroto (.
), e aplicando uma função de criptografia com uma chave.
Encrypt(header-em-base64 + '.' + payload-em-base64, secret-ou-chave-privada);
(Isso aí é só ilustrativo, não vá copiar e colar achando que fez segurança.)
Resultado? O payload continua lá, totalmente legível, pronto pra quem quiser decodificar e ver as informações dele. A diferença é que você vai garantir em todo o ciclo de vida dele que não foi alterado.
Validação: onde entra a chave?
A chave privada serve pra assinar o token. É com ela que você diz: "isso aqui saiu da minha aplicação".
Já a chave pública é usada pra validar.
Qualquer client, SPA ou serviço pode verificar se o token é legítimo, sem precisar da chave privada.
Simples assim, é assim que a criptografia moderna funciona.
Estrutura do JWS
Aqui entra a parte que ninguém lê na documentação, mas todo mundo deveria.
O JWS é dividido em três partes:
Header
Payload
Assinatura Digital
O algoritmo usado aqui é o ECDSA com curva P-256 e SHA-256, conhecido como ES256. Não, não é ES de "ECMA Script", é de "Elliptic Curve", tá?
E o formato final?
O resultado são três blocos de base64url, separados por .
. Simples na teoria, fácil de copiar errado na prática.
Propositalmente nos exemplos usei uma criptografia Assimétrica. Um formato que contém duas chaves, uma pública e outra privada. Que diga-se de passagem é o mais recomendado pela RFC. A internet está infestada de exemplos cagados usando HMAC. Eu também falo mais sobre isso no post Como assinar digitalmente seu JWT.
JSON Web Encryption (JWE)
"Se assinar não resolve, criptografa. E se criptografar não resolve... bom, pelo menos você tentou."
Sabe aquele seu JWT bonitinho, legível, com as claims tudo expostas pra quem quiser ver? Pois é, aquilo é um JWS. E é ótimo... até você perceber que tá mandando CPF, endereço, preferências políticas e o nome do cachorro do usuário no payload.
Aí você descobre o JWE, o irmão mais paranoico do JWT. Porque ele não só assina... ele enfia tudo num cofre digital, tranca e joga a chave fora (mentira, a chave tá lá, mas bem guardada, espero).
Estrutura do JWE
O JWE é mais complexo. Em vez dos três campos básicos, como o JWS, ele vem com dados da criptografia:
- Header
- Chave criptografada
- Initialization Vector
- Additional Authenticated Data (Sim, estamos falando do AES256)
- Ciphertext (que é o payload todo cagado, digo embaralhado)
- Auth Tag (Para proteger de certos ataques, como integrity attack)
É isso aí: seis partes separadas por ponto. Se você achava que base64url era tranquilo, agora você vai amar concatenar meia dúzia de trechos indecifráveis.
Para os mais malandros, o JWE é uma criptografia hibrida. Diria que hoje é uma das melhores opções nesse sentido, uma vez que já temos um conjunto completo de frameworks que implementam o JWE, assim não precisamos fazer na mão!
Outro dia eu escrevo sobre ele, só não sei quando, mas pesquisa ai no blog e vê se eu já escrevi.
E o Header? É um mini-RPG:
- Protected Header
- Shared Unprotected Header
- Per-Recipient Unprotected Header
Cada um com suas magias, buffs e penalidades, e claro, nem todos são obrigatórios. Depende do algoritmo, da fase da lua, e do humor do dev que implementou baseado na RFC (Se é que ele seguiu ela).
Resultado final?
Basicamente, tudo em base64url, separado por .
, com cara de que você acabou de invocar um demônio binário.
JSON Web Algorithms (JWA)
"Não é porque você pode usar qualquer algoritmo que você deve. Mas vai tentar explicar isso pra quem assina token com
HS256
e acha que tá seguro..."
Olha só... Não é qualquer algoritmo meia-boca que pode sair assinando ou criptografando JWT por aí. Existe uma RFC inteira para isso. Tá lá, bonitinha, cheia de letrinha chata dizendo quais algoritmos são válidos, quais são seguros e quais você devia evitar.
Mas como sempre, o pessoal ignora a parte da recomendação e vai direto no "que funciona". Resultado? Uma internet cheia de tutorial (CTRL-C e CRTL-V para hypar nas redes sociais) ensinando a usar HS256
, e é de cair o toba da bundo quando o infeliz ainda gera uma secret com caracteres printáveis em algum arquivo de configuração, tipo "minh@S3nh@123"
, mas os super dotados do CTRL-C e CTRL-V perceberam que uma string assim é ruim, dai foram lá e pioraram a bagaça, ensinaram a usar um GUID (PQP!). Mas o porque não usar o GUID é papo para outro post.
O que a RFC recomenda?
Apesar de listar vários algoritmos, a própria RFC recomenda com todas as letras o uso de:
- ES256 – ECDSA usando curva P-256 com SHA-256
- PS256 – RSASSA-PSS com SHA-256
Ou seja, criptografia assimétrica de verdade. Nada de HMAC caseiro. Você assina com a chave privada, valida com a pública. Seguro, quando usar HMAC? Já escrevi sobre isso no post Como assinar digitalmente seu JWT.
Algoritmos válidos para assinar um JWS
Algoritmo | Descrição |
---|---|
HS256 | HMAC usando SHA-256, o favorito dos preguiçosos |
HS384 | HMAC usando SHA-384, pra quem quer fingir que melhorou |
HS512 | HMAC usando SHA-512, mesmo erro, com SHA maior |
RS256 | RSASSA-PKCS1-v1_5 com SHA-256, já é algo decente |
RS384 | RSASSA-PKCS1-v1_5 com SHA-384 |
RS512 | RSASSA-PKCS1-v1_5 com SHA-512 |
ES256 | ECDSA com curva P-256 e SHA-256, a escolha certa, segundo a RFC |
ES384 | ECDSA com P-384 e SHA-384 |
ES512 | ECDSA com P-521 e SHA-512, porque sempre tem alguém que quer ir até o 512 |
PS256 | RSASSA-PSS com SHA-256, outro queridinho da RFC |
PS384 | RSASSA-PSS com SHA-384 |
PS512 | RSASSA-PSS com SHA-512 |
none | Sem assinatura, ideal pra quem quer emoção e vazamento de dados |
Um parêntese sobre o famigerado none
Sim, a RFC prevê o algoritmo "none"
. Isso mesmo, um JWT sem assinatura nenhuma. Porque aparentemente alguém achou que seria uma boa ideia deixar o token sem nenhuma verificação de integridade e ainda assim usá-lo pra autenticação.
A justificativa oficial?
“Supomos que outra camada de segurança está protegendo o token.”
Ah tá. Tipo... esperança?
Geralmente quando vemos criticas ao uso de JWT na comunidade técnica 99,9% vão criticar a RFC aceitar o none, mas dai é igual criticar o cara que criou uma linguagem de programação porque os programadores que utilizam ela são uns tapado.
JSON Web Key (JWK)
Vamos lá: JWK é um objeto JSON que representa uma chave criptográfica. Isso mesmo, uma chave. Não é mágica, não é middleware, não é "autenticação". É só uma estrutura com os dados necessários pra assinar digitalmente um JWS ou criptografar um JWE.
E não, não é "só pegar uma string aleatória e chamar de chave". O bagulho é mais embaixo, tem até RFC dizendo como fazer direito.
Dentro do mundinho lindo do OAuth 2.0, quem gera e gerencia o JWK é o Authorization Server. Por quê? Porque todos os JWTs são emitidos por ele. Só ele sabe a chave privada. Só ele pode assinar tokens válidos. É o Senhor dos JWTs, basicamente.
Os consumers (clients, APIs, SPAs, seu app em React que você jura que é seguro) usam a chave pública do JWK pra validar se o token foi mesmo assinado por ele ou se foi inventado por algum Zé com insomnia e acesso ao Postman.
JWK pode ser público? Pode. E deve.
A parte pública do JWK pode, e deve, ser exposta, geralmente por meio de um endpoint conhecido como JWKS (JWK Set). É com essa informação que os consumidores do token vão poder verificar se o token não foi adulterado no caminho.
Sim, isso mesmo: a segurança do seu sistema pode depender de um JSON público contendo a chave pública. Mas calma, é seguro, a menos que você faça besteira.
É seguro mesmo?
Se não fosse seguro o Google não deixaria o deles aberto na internet, né?
Esse é o tipo de chave que você expõe pra quem quiser validar seus tokens. O formato é padronizado, a estrutura tem nome pra tudo, e sim, o kid
é importante. Não ignora.
Dá um ligo ao vivo:
- OAuth2 well known: https://accounts.google.com/.well-known/openid-configuration
- Chaves de criptografia (parte pública): https://www.googleapis.com/oauth2/v3/certs
Exemplo de JWK
Geralmente a gente usa o openssl para gerar, mas dá para ser via código também.
Essa aqui, meu amigo, você guarda a sete chaves.
Nada de colocar isso em .env
, subir pro GitHub e depois correr pra pedir ajuda no Stack Overflow dizendo que "alguém assinou meu token".
Se essa chave vazar, qualquer um pode criar um JWT "válido' em nome do seu sistema.
JSON Web Key Set (JWKS)
JWKS é basicamente um array de JWK. Simples assim. Um monte de chave pública empacotada num JSON bonitinho, publicado num endpoint público, geralmente por um Authorization Server dentro do ecossistema OAuth 2.0.
Mas por que isso existe? Porque, adivinha só: Algoritmos envelhecem. O que era seguro em 2010, hoje é piada pra GPU de placa de vídeo gamer. A galera quebra SHA-1 no café da manhã e já começa a brincar com SHA-256 no almoço.
Por que usar JWKS?
Pra evitar que seu sistema dependa de uma única chave eterna e imutável. O JWKS permite que você tenha várias chaves ativas ao mesmo tempo, cada uma com seu kid
(Key ID), facilitando:
- Rotação de chaves, sem quebrar tudo
- Suporte a múltiplos algoritmos
- Transição suave entre o que era seguro e o que ainda é seguro
E qual a prática recomendada?
As boas práticas (e o bom senso) mandam que você rotacione suas chaves a cada 90 dias. Não é difícil. Não dói. E com o JWKS, você pode incluir a nova chave, deixar a antiga por um tempo e, quando todos os sistemas já estiverem usando a nova, aposenta a velha sem gerar caos.
"Ah, mas e os tokens assinados com a chave antiga?"
Eles ficam válidos até expirarem. Porque o JWKS mantém a chave antiga lá pra validação. Isso se chama transição controlada, não é novidade. É um conceito simples, mas que só desenhando as pessoas entendem. E eu odeio desenhar.