O que é JWT?

Saber o que é JWT é essencial para qualquer desenvolvedor web, porque o JSON Web Token é um dos padrões de token mais utilizados no mundo para autenticar usuários e proteger APIs. Neste artigo, vamos entender sua estrutura, o papel da assinatura e os cuidados de segurança que todo projeto precisa ter.

O padrão está descrito na RFC 7519 do IETF, publicada em maio de 2015. Ainda assim, muita gente usa o JWT no dia a dia sem entender o que acontece por baixo dos panos.

O que é JWT e por que ele existe

Quando falamos de tokens em aplicações web, uma dúvida aparece logo de cara. Então você está me dizendo que eu vou armazenar um token em um local onde o usuário, ou outra pessoa, pode ir lá e visualizar?

Isso mesmo. Os tokens são como chaves de acesso, com informações e uma duração, ou seja, se alguém obtiver o seu token, pode se passar por você.

Por isso, a segurança física é a primeira e mais importante etapa que temos. Se alguém tem acesso ao seu navegador, fisicamente ou remotamente, essa pessoa consegue ver o seu token.

Na verdade, esse seria o menor dos seus problemas. Afinal, os dados de navegação ficam armazenados localmente, e todas as suas sessões estão em arquivos no seu computador.

Basta alguém copiar esses arquivos da sua máquina para a dele e pronto, ele estará logado com todas as suas sessões. Por esse motivo, sempre recomendamos não clicar em links suspeitos, já que uma simples cópia expõe todas as suas informações.

Voltando aos tokens, você pode armazenar uma chave e um valor no Local Storage do navegador. Nesse caso, o ideal é que o conteúdo não fique legível à primeira vista.

Dessa forma, quem pegar o seu token não verá nada além de uma sequência de caracteres como esta:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Como esse processo é comum entre aplicações e APIs, criou-se um padrão chamado JWT, que se pronuncia "jót". A sigla vem de JSON Web Token.

Em outras palavras, o JWT é um formato compacto e seguro para URLs que transporta afirmações, chamadas de claims, entre duas partes. Essas claims ficam em um objeto JSON, protegido por uma assinatura digital.

A estrutura de um JWT

Ao decodificar o token acima, temos como resultado os seguintes objetos JSON:

{
  "alg": "HS256",
  "typ": "JWT"
}

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Se você notar, o token usa pontos para separar suas regiões. Assim, ele se divide em três partes principais: header, payload e assinatura.

Token JWT no jwt.io com o header em vermelho, o payload em rosa e a assinatura em azul, ilustrando o que é JWT na prática
As três partes de um token, separadas por pontos e destacadas por cor.

Um ponto importante precisa ficar claro desde já. O header e o payload não são criptografados, e sim codificados em Base64Url, um formato que qualquer pessoa consegue reverter.

Por isso, o JWT protege a integridade dos dados, mas não o sigilo. Quando o conteúdo precisa ser secreto, existe uma variante chamada JWE, que de fato criptografa o token.

Header: o cabeçalho do token

A primeira parte, em vermelho, é o header ou cabeçalho. Ela define o algoritmo usado na assinatura e o tipo do token, que no nosso caso é JWT.

Quando decodificamos essa parte, temos o seguinte JSON como resultado:

{
  "alg": "HS256",
  "typ": "JWT"
}

Podemos mudar esses valores, incluindo o algoritmo, e com isso teremos uma sequência diferente. Por exemplo, ao trocar HS256 por HS384, temos o seguinte header:

// eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9
{
  "alg": "HS384",
  "typ": "JWT"
}

O HS256 é o mais comum e equilibra desempenho e segurança. Afinal, quanto maior o tamanho do hash, mais processamento ele exige.

Além dos algoritmos HMAC, existem opções assimétricas como RS256 e ES256. Nelas, uma chave privada assina o token e uma chave pública valida, o que é útil quando vários serviços precisam conferir a assinatura.

Payload: a carga de informações

O segundo item, em rosa, é o payload ou carga. Ele contém as informações que podemos incluir no token, com algumas ressalvas que veremos adiante.

// eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

Assim como no header, o payload também varia de acordo com as informações que colocamos nele. Ao incluir a informação premium, temos outro valor gerado:

// eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsInByZW1pdW0iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "premium": true,
  "iat": 1516239022
}

A RFC define algumas claims registradas, que têm significado padronizado. Entre elas estão iss (emissor), sub (assunto), aud (público), exp (expiração), nbf (não antes de), iat (emitido em) e jti (identificador único).

Por outro lado, claims como name, admin e premium são privadas. Ou seja, quem combina o significado delas são as aplicações que emitem e consomem o token.

Assinatura: a garantia de integridade

Por fim, temos a parte em azul, que representa a assinatura do token. O servidor calcula esse valor a partir das outras duas partes e de uma chave secreta que só ele conhece.

Em resumo, aplicamos um HMAC com SHA256 sobre o header e o payload convertidos para Base64Url. O cálculo é simples assim:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  'MY SECRET KEY'
)

O resultado também passa por Base64Url e vira a terceira parte do token. Com isso, qualquer alteração no header ou no payload muda a assinatura esperada.

O que é JWT na prática: visualizando e alterando um token

Então quer dizer que, além de salvar o token, eu ainda posso visualizar o conteúdo dele? Sim, sites como o jwt.io permitem visualizar tudo o que um token contém.

Indo além, o jwt.io permite alterar o conteúdo de um token e adicionar informações extras. Você também pode fazer isso manualmente, já que basta decodificar, editar e codificar de novo.

Isso significa que eu posso pegar o token abaixo, gerado para mim, que diz que eu só tenho o perfil student:

Token aberto no jwt.io com o payload contendo o nome André Baltieri e apenas o perfil student
Token original, emitido com o perfil de estudante.
{
  "name": "André Baltieri",
  "roles": ["student"],
  "iat": 1516239022
}

Em seguida, adiciono o perfil admin:

{
  "name": "André Baltieri",
  "roles": ["student", "admin"],
  "iat": 1516239022
}

Agora eu tenho um novo token, aparentemente com acesso de administrador:

Token editado no jwt.io com os perfis student e admin no payload e uma nova sequência codificada
Token modificado no cliente, agora com o perfil de administrador.

Entendendo os payloads e a assinatura

Muita calma nessa hora! É possível sim mudar um token, mas para gerar uma assinatura válida é necessária a chave secreta, que somente o servidor deve conter.

Se alguém assinar o token com qualquer outra chave, diferente daquela usada na emissão, a validação falha. É isso que torna os tokens seguros contra alterações.

Então não importa se alguém alterou o token no cliente ou no meio do caminho. Se ninguém assinar o token novamente com a chave correta, o servidor vai considerá-lo inválido.

Man in the middle: existe um ataque comum chamado "homem no meio", que intercepta a comunicação entre o cliente e o servidor para roubar ou modificar informações.

A assinatura impede a modificação silenciosa, mas não impede o roubo. Por isso, sempre trafegue tokens sobre HTTPS, que protege o canal entre as partes.

Outro cuidado importante envolve o campo alg. O servidor nunca deve aceitar tokens com alg igual a none nem deixar que o próprio token escolha o algoritmo de validação.

Gerando e validando um JWT no .NET

Para tornar o processo concreto, veja um exemplo em C# com o pacote Microsoft.IdentityModel.JsonWebTokens. Ele emite um token assinado com HS256 e depois valida esse token.

using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

// A chave precisa ter pelo menos 256 bits (32 bytes) para HS256
var secret = "a-very-long-secret-key-with-at-least-32-bytes";
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));

var handler = new JsonWebTokenHandler();

var token = handler.CreateToken(new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity([
        new Claim("sub", "1234567890"),
        new Claim("name", "John Doe")
    ]),
    Expires = DateTime.UtcNow.AddHours(2),
    SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
});

// A validação confere assinatura e expiração
var result = await handler.ValidateTokenAsync(token, new TokenValidationParameters
{
    ValidateIssuer = false,
    ValidateAudience = false,
    IssuerSigningKey = key
});

Console.WriteLine(result.IsValid);

Em produção, a chave fica em um cofre de segredos, nunca no código. Além disso, vale validar emissor e público, que deixamos desligados apenas para simplificar o exemplo.

Se você quer ver o fluxo completo em uma API, o artigo sobre autenticação e autorização com JWT e Bearer no ASP.NET 7 mostra a configuração passo a passo. Para APIs enxutas, também existe o guia de Minimal API com autenticação JWT e Bearer.

Não armazene valores sensíveis no payload

Outro ponto importantíssimo é o uso do payload. Embora você possa adicionar qualquer informação nele, não é recomendado trafegar dados sensíveis ali.

Lembre que qualquer pessoa com o token consegue ler o payload. Cartão de crédito, telefone, endereço ou qualquer dado que comprometa o usuário deve ficar apenas no servidor.

Em suma, ter o e-mail ou o ID do usuário no payload já basta. Com essas informações, o servidor consulta o que precisar sobre ele.

Também vale manter o token pequeno. Afinal, ele viaja em toda requisição, normalmente no cabeçalho Authorization com o esquema Bearer.

Tempo de vida do JWT

Uma recomendação e padrão dos JWTs é conter no payload a informação iat, que significa "Issued At" ou "emitido em". Trata-se de um timestamp com a data e a hora em que o servidor gerou o token.

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022 👈
}

Com esse dado, podemos criar uma validação dizendo que o token só pode existir por um determinado tempo. Assim, se alguém roubar o token, ele só vai servir durante alguns dias ou horas.

Na prática, a RFC oferece a claim exp justamente para isso. Ela indica o momento a partir do qual o servidor deve recusar o token, e as bibliotecas a validam automaticamente.

Dependendo do sistema, esse tempo de expiração pode variar. Em geral, ele não deve ser tão curto a ponto de obrigar o usuário a se autenticar o tempo todo, nem tão longo a ponto de alguém usar um token roubado por meses.

Uma estratégia comum é combinar um token de acesso de vida curta com um refresh token. Dessa forma, o usuário continua logado, enquanto o token exposto nas requisições expira rapidamente.

Como o JWT funciona no fluxo de autenticação

Até aqui olhamos o token de forma isolada. Agora vale ver como ele participa de uma autenticação completa entre o front-end e a API.

Primeiro, o usuário informa suas credenciais em uma tela de login. Em seguida, o front-end envia esses dados para um endpoint de autenticação da API.

O servidor confere usuário e senha no banco de dados. Se estiver tudo certo, ele monta as claims, define a expiração e assina o token com a chave secreta.

Então, a API devolve o token para o cliente, que o guarda no navegador ou no aplicativo. A partir desse momento, o usuário não precisa enviar a senha de novo.

Em cada requisição seguinte, o cliente envia o token no cabeçalho HTTP, neste formato:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Por fim, a API recalcula a assinatura, confere a expiração e lê as claims. Com base nelas, decide se o usuário pode ou não acessar aquele recurso.

Repare que o servidor não precisou consultar nenhuma sessão armazenada. Tudo o que ele precisa para validar a requisição está no próprio token e na chave que ele guarda.

JWT ou sessão no servidor

Essa característica stateless é a grande vantagem do JWT. Como a API não guarda estado de sessão, fica mais simples escalar horizontalmente e distribuir a carga entre várias instâncias.

Além disso, o mesmo token pode valer para vários serviços que confiam no mesmo emissor. Por isso, o padrão aparece com frequência em arquiteturas de microsserviços e em provedores de identidade.

Por outro lado, essa mesma característica traz um desafio. Uma vez emitido, o token continua válido até expirar, mesmo que o usuário faça logout ou tenha o acesso bloqueado.

Para contornar isso, as equipes costumam usar tokens de vida curta. Também é comum manter uma lista de tokens revogados, consultada pelo identificador da claim jti.

Já a sessão tradicional guarda o estado no servidor e entrega ao navegador apenas um identificador. Nesse caso, revogar o acesso é imediato, mas a aplicação precisa compartilhar essas sessões entre as instâncias.

Não existe resposta única para todos os cenários. Em geral, APIs consumidas por aplicativos e SPAs se beneficiam do JWT, enquanto aplicações web tradicionais funcionam bem com sessões e cookies.

Boas práticas de segurança com JWT

Depois de entender o que é JWT, vale reunir os cuidados em uma lista objetiva. Eles evitam a maioria dos problemas que aparecem em produção.

  • Use sempre HTTPS para trafegar tokens.
  • Guarde a chave de assinatura fora do código e troque-a periodicamente.
  • Defina exp e valide emissor, público e assinatura em toda requisição.
  • Não coloque dados sensíveis no payload.
  • Rejeite tokens com algoritmo none ou com algoritmo diferente do esperado.
  • Proteja o front-end contra XSS, já que scripts maliciosos conseguem ler o Local Storage.

Para uma visão mais ampla sobre o tema, confira as boas práticas de segurança em APIs ASP.NET Core. Lá, o JWT aparece ao lado de outras camadas de proteção.

Conclusão

Neste artigo, entendemos o que é JWT e como ele funciona. Vimos como o servidor cria os tokens, quais informações eles contêm e por que a assinatura impede alterações.

Também vimos que o payload é legível por qualquer pessoa, por isso ele nunca deve carregar dados sensíveis. Além disso, um tempo de vida bem definido limita o estrago caso alguém roube um token.

Com esses fundamentos, fica muito mais fácil configurar autenticação em qualquer stack. Afinal, o padrão é o mesmo, seja em .NET, Node.js ou qualquer outra plataforma.

FAQ

JWT é criptografado?

Não por padrão. O header e o payload são apenas codificados em Base64Url, e a assinatura garante integridade, não sigilo.

Qual a diferença entre iat e exp?

A claim iat registra quando o token foi emitido. Já a claim exp define o momento em que ele deixa de ser aceito.

Onde devo guardar o JWT no navegador?

Local Storage é simples, mas fica exposto a ataques XSS. Cookies com HttpOnly, Secure e SameSite reduzem esse risco.

Alguém pode alterar meu token?

Pode alterar o conteúdo, mas sem a chave secreta não consegue gerar uma assinatura válida. Assim, o servidor rejeita o token adulterado.

HS256 ou RS256?

HS256 usa uma única chave secreta compartilhada. RS256 usa um par de chaves e facilita a validação por vários serviços sem expor a chave de assinatura.