Um JWT são três blocos de texto em base64url unidos por pontos. O primeiro é um header (cabeçalho) que nomeia o algoritmo que assinou o token, o segundo é o payload (a carga útil) — um objeto JSON simples de claims, como quem é o usuário e quando o token deixa de valer — e o terceiro é uma assinatura sobre os dois primeiros. Só a assinatura precisa de uma chave. O header e o payload são codificados, não criptografados, então qualquer pessoa com o token consegue ler todos os claims dentro dele, inclusive a pessoa para quem ele foi emitido.
Essa última frase é a que as pessoas entendem errado. Um JWT guardado no local storage do navegador é legível pelo usuário, por qualquer script da página e por qualquer um que ponha a mão nele depois. Codificar não é trancar.
Quais são as três partes de um JWT?
O header
Normalmente dois campos. alg nomeia o algoritmo de assinatura — HS256, RS256 e ES256 cobrem quase tudo o que você vai encontrar por aí — e typ é quase sempre a string JWT. Tokens de um provedor de identidade costumam acrescentar kid, um identificador de chave que diz ao verificador qual das várias chaves públicas do emissor usar.
A codificação é base64url: base64 comum com - e _ no lugar de + e /, e sem o preenchimento final de =, para que o token possa entrar em uma URL ou em um header sem escape. Se esse esquema não é familiar, base64 e para que ele serve de verdade é a leitura curta para fazer antes.
O payload
Um objeto JSON cujas chaves são chamadas de claims. Alguns são padronizados, a maioria é o que o emissor resolveu colocar ali: um ID de usuário, um endereço de e-mail, um papel, um tenant, uma lista de scopes.
Você não precisa de chave nem de biblioteca para olhar. Cole o token no decodificador de JWT daqui e o header, o payload e cada claim saem em uma tabela, com os timestamps convertidos em datas de verdade. Ele roda inteiro no navegador e não faz nenhuma requisição de rede, que é o único jeito sensato de olhar um token que ainda está vivo.
A assinatura
Bytes crus, codificados em base64url, calculados sobre a string ASCII exata header.payload — as formas codificadas, não o JSON. Mude um caractere do payload e a assinatura não bate mais.
O comprimento dela já restringe o algoritmo antes de você ler o header. HS256 produz 32 bytes, que saem como 43 caracteres. ES256 produz 64 bytes, ou 86 caracteres. RS256 com uma chave de 2048 bits produz 256 bytes, ou 342 caracteres — é por isso que tokens RS256 são bem mais volumosos do que os HS256 carregando claims idênticos. Mesmo assim, comprimento sozinho não é prova: HS512 também cai em 86 caracteres, então HS512 e ES256 parecem idênticos vistos de fora.
Quais claims são padrão?
A RFC 7519 registra sete, e todos eles são opcionais. Um token sem nenhum deles continua sendo um JWT válido.
iss— issuer, o emissor. Quem cunhou o token, normalmente uma URL.sub— subject, o sujeito. Sobre quem ou sobre o que o token fala, tipicamente um ID de usuário.aud— audience, a audiência. Qual aplicação deve aceitá-lo.exp— hora de expiração.nbf— not before. O token não vale até esse momento.iat— issued at, quando foi emitido.jti— um ID único do token, usado para detectar replay.
Todo o resto é invenção do próprio emissor. O OpenID Connect acrescenta um conjunto conhecido — email, name, nonce, auth_time — e o seu backend provavelmente acrescenta mais dois ou três. Não existe esquema nenhum contra o qual validar, então um decodificador só consegue rotular os claims que reconhece; um claim próprio sai com o valor cru e sem explicação, porque não há onde procurar uma.
Por que os timestamps parecem sem sentido?
exp, nbf e iat são valores NumericDate: segundos desde 1º de janeiro de 1970 UTC. Segundos, não milissegundos. Timestamps Unix e como ler um cobre o formato, mas a pegadinha específica de JWT merece ser dita com todas as letras.
O Date.now() do JavaScript devolve milissegundos. Coloque isso direto no exp e você cunha um token que diz expirar daqui a dezenas de milhares de anos — a contagem de milissegundos de hoje, lida como segundos, cai em algum ponto depois do ano 58.000. Todo verificador aceita, nada registra um aviso, e a duração da sua sessão passa a ser, na prática, infinita. O número é mil vezes grande demais e parece perfeitamente plausível à primeira vista: dez dígitos são uma data desta década, treze dígitos são um bug.
A outra coisa a saber sobre esses campos é o desvio de relógio. Relógios de servidor derivam, então os verificadores podem ser configurados para permitir uma pequena tolerância nas duas direções, e 60 segundos é um ajuste comum. Várias bibliotecas populares vêm sem tolerância nenhuma por padrão, então confira a sua em vez de supor: um token que a sua máquina chama de expirado pode ser aceito assim mesmo, e um que parece novo pode ser recusado.
O que uma assinatura válida prova de fato?
Uma coisa só: que o header e o payload não foram alterados desde que alguém com a chave os assinou. Isso é genuinamente útil e é também toda a garantia.
Ela não prova quem está apresentando o token. Um JWT é uma credencial ao portador — quem estiver com ele pode usar. Um token roubado é um token válido, e a checagem da assinatura vai passar numa boa para o ladrão.
Ela também não prova que o token era para você. Conferir a assinatura não é o mesmo que aceitar o token: você ainda precisa confirmar que iss é o emissor em que você confia e que aud é a sua aplicação. Um token corretamente assinado mas emitido para outro serviço continua sendo o token errado, e um monte de integrações pulou essa conferência.
O campo alg é um claim, não um fato
O header é fornecido por quem enviou o token, o que significa que o atacante também o controla. Dois ataques bem conhecidos vêm de confiar nele. O primeiro é alg: none: as especificações JOSE incluem um modo sem proteção cuja assinatura é a string vazia, e uma biblioteca que lê o algoritmo do header vai aceitar um token que um atacante digitou na mão. O segundo é rebaixar RS256 para HS256 — se o servidor verifica o que quer que o header peça, um atacante reassina o token com HS256 usando a própria chave pública do servidor como segredo HMAC. A chave é pública, então o ataque sai de graça.
A correção é a mesma em toda biblioteca: fixe o algoritmo que você espera em vez de lê-lo do token.
Por que ninguém consegue cancelar um token?
Um JWT é autocontido por design. O servidor confere a assinatura e os timestamps e não toca em banco de dados nenhum, que é todo o argumento de desempenho para usar um. O custo é que deslogar alguém, tirar um papel ou banir uma conta não tem efeito até o token expirar, porque nada é consultado na hora da verificação.
Toda solução de contorno abre mão de parte do benefício. Expiração curta mais um refresh token move a consulta ao banco para o passo de renovação. Uma lista de jti revogados devolve a consulta exatamente para onde você estava evitando. Um claim com a versão do token comparado com o registro do usuário faz a mesma coisa com menos linhas. Não existe arranjo que mantenha a ausência de estado e consiga revogação instantânea.
O que nunca deve entrar no payload de um JWT?
Qualquer coisa que você não colaria em um chat público. IDs internos de usuário, endereços de e-mail e flags de papel vazam dos tokens o tempo todo, porque quem os adiciona está pensando no payload como dado do servidor.
Tamanho também importa mais do que as pessoas esperam. O token viaja em toda requisição, base64 acrescenta cerca de um terço a qualquer coisa que você codifique, e um cookie de navegador é limitado a mais ou menos 4 KB. Enfie um perfil de usuário inteiro e uma lista de permissões no payload e uma hora você vai encontrar um proxy que recusa o header da requisição em vez de uma mensagem de erro limpa.
O que um decodificador não consegue te dizer?
Ler um token localmente responde a maioria das perguntas, mas tenha clareza sobre o que ele não consegue fazer. Um decodificador que roda no navegador não vai buscar as chaves do emissor: um verificador de verdade lê o kid e puxa a chave correspondente de um endpoint JWKS pela rede, então aqui você mesmo precisa colar o segredo ou a chave pública. Ele também não tem opinião sobre se iss e aud são os valores que a sua aplicação deveria aceitar — essa conferência só existe no seu código.
Tokens criptografados são outro objeto completamente diferente. Um JWE tem cinco partes em vez de três e o payload dele é texto cifrado, então não há nada para ler sem a chave de descriptografia.
E uma pegadinha prática com segredos HMAC: alguns emissores guardam o segredo codificado em base64 e o decodificam antes de assinar. Se um segredo que deveria funcionar não verifica em lugar nenhum, o problema são os bytes que estão sendo hasheados, e não o token — decodifique o segredo primeiro, que no decodificador acima é a caixa "O segredo está em base64".
Se você tem um token na sua frente agora, o decodificador de JWT vai abrir o header, o payload e os claims dele, converter os timestamps, sinalizar um token expirado ou sem assinatura, e conferir a assinatura contra um segredo ou uma chave pública que você colar. Nada é enviado para lugar nenhum, embora um token vivo na sua área de transferência já seja menos privado do que um que ficou dentro de um header HTTP.
O claim que mais derruba as pessoas é exp, e no fundo é um problema de timestamp Unix, não de JWT. O que é um timestamp Unix e como ler um explica por que dez dígitos e treze dígitos significam datas absurdamente diferentes, que é a diferença entre uma sessão de uma hora e uma que nunca acaba.
Perguntas frequentes
Que informação fica guardada em um token JWT?
Um header que nomeia o algoritmo de assinatura, um payload de claims e uma assinatura. Os claims normalmente incluem um ID de usuário, um emissor, uma audiência e os timestamps de expiração e de emissão, além do que mais o emissor tiver acrescentado, como um endereço de e-mail, um papel ou uma lista de scopes. Não existe esquema fixo, então dois tokens de serviços diferentes podem não se parecer em nada.
Qualquer pessoa consegue ler um JWT sem o segredo?
Consegue. O header e o payload são codificados em base64url, um formato de texto que qualquer um reverte em um segundo. O segredo ou a chave pública só são necessários para confirmar que o conteúdo não mudou depois de assinado, não para lê-lo.
Um JWT é criptografado?
Um JWT assinado padrão não é criptografado, apenas codificado e assinado. Existe um formato separado, o JWE, cujo payload é de fato texto cifrado; dá para distinguir contando os pontos, já que um JWE tem cinco partes em vez de três. Se um valor precisa ficar em segredo, ele não entra em um token assinado.
O que a assinatura de um JWT prova?
Apenas que o header e o payload não foram alterados desde que alguém com a chave os assinou. Ela não diz nada sobre quem está apresentando o token, então um JWT roubado passa na verificação exatamente como um legítimo. Ela também não confirma que o emissor e a audiência são os que a sua aplicação espera, o que é uma conferência separada que você precisa fazer.
Por quanto tempo um JWT deve valer?
Pouco, porque um JWT não pode ser cancelado antes de expirar. Access tokens costumam receber vidas de alguns minutos até uma hora, com um refresh token de vida mais longa cuidando da renovação, para que a checagem de revogação aconteça ali. O número certo é um equilíbrio entre de quanto em quanto tempo você quer ir ao banco de dados e por quanto tempo um token roubado continua útil.
Por que meu JWT nunca expira?
A causa de sempre é colocar milissegundos no claim exp. Os timestamps de um JWT são contados em segundos desde 1º de janeiro de 1970, então um valor de treze dígitos em milissegundos é lido como uma data a dezenas de milhares de anos de distância. Um exp de dez dígitos é uma data desta década; um de treze dígitos é um bug.
Última atualização 19 de setembro de 2026