JSON Web Tokens (JWT): estrutura, assinatura e validação

1. Introdução ao JWT e sua Relevância em Segurança

JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define uma forma compacta e autossuficiente de transmitir informações entre partes como um objeto JSON. É amplamente utilizado em autenticação e autorização em aplicações web modernas, especialmente em arquiteturas de microsserviços e APIs RESTful.

É importante entender a diferença entre os padrões relacionados:
- JWT (JSON Web Token): o conceito geral de token codificado em JSON
- JWS (JSON Web Signature): JWT assinado, garantindo integridade e autenticidade
- JWE (JSON Web Encryption): JWT criptografado, garantindo confidencialidade

O uso de JWT sem validação adequada expõe a aplicação a riscos graves como falsificação de tokens, ataques de injeção e vazamento de dados sensíveis.

2. Estrutura do JWT: Header, Payload e Signature

Um JWT é composto por três partes separadas por pontos (.):

header.payload.signature

O header contém metadados sobre o token:

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg: algoritmo de assinatura (HS256, RS256, ES256, etc.)
  • typ: tipo do token (geralmente "JWT")

Payload

O payload contém as claims (declarações) sobre a entidade e dados adicionais:

{
  "sub": "1234567890",
  "name": "João Silva",
  "iat": 1516239022,
  "exp": 1516242622,
  "admin": true
}

Tipos de claims:
- Registradas: sub, iss, exp, nbf, iat, jti, aud
- Públicas: definidas no IANA JSON Web Token Registry
- Privadas: claims customizadas acordadas entre as partes

Nunca armazene informações sensíveis no payload, pois ele é apenas codificado em base64, não criptografado.

Signature

A assinatura é criada combinando header e payload com uma chave secreta:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

3. Algoritmos de Assinatura e Implicações de Segurança

HMAC com SHA-256 (HS256)

Algoritmo simétrico: mesma chave para assinar e verificar.

// Geração do token com HS256
const token = jwt.sign({ userId: 123 }, 'minha-chave-secreta', { algorithm: 'HS256' });

Riscos: se a chave secreta vazar, qualquer um pode forjar tokens válidos.

RSA e ECDSA (RS256, ES256)

Algoritmos assimétricos: par de chaves pública/privada.

// Geração com RS256
const token = jwt.sign({ userId: 123 }, chavePrivada, { algorithm: 'RS256' });
// Verificação
const decoded = jwt.verify(token, chavePublica, { algorithms: ['RS256'] });

Vantagem: apenas quem possui a chave privada pode emitir tokens; qualquer um pode verificar com a chave pública.

Ataque "alg:none"

Uma vulnerabilidade crítica ocorre quando a biblioteca aceita tokens com alg: none:

// Token malicioso
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VySWQiOjF9.

Prevenção: sempre especificar os algoritmos permitidos na verificação:

jwt.verify(token, chavePublica, { algorithms: ['RS256'] });

4. Validação Segura de JWT no Backend

A validação deve seguir uma sequência rigorosa:

// Exemplo de validação segura em Node.js
function validateToken(token) {
  try {
    const decoded = jwt.verify(token, chavePublica, {
      algorithms: ['RS256'],
      issuer: 'https://meu-auth-server.com',
      audience: 'https://minha-api.com'
    });

    // Verificações adicionais
    if (decoded.exp < Date.now() / 1000) {
      throw new Error('Token expirado');
    }

    if (decoded.nbf && decoded.nbf > Date.now() / 1000) {
      throw new Error('Token não está ativo ainda');
    }

    return decoded;
  } catch (error) {
    // Log do erro sem expor detalhes sensíveis
    console.error('Token inválido:', error.message);
    return null;
  }
}

Validações essenciais:
- exp: expiration time
- nbf: not before
- iss: issuer (emissor)
- aud: audience (público-alvo)
- iat: issued at (emitido em)

Cuidados com bibliotecas: algumas bibliotecas podem ter vulnerabilidades de desserialização insegura. Sempre use versões atualizadas e mantenha dependências auditadas.

5. Ciclo de Vida do Token: Emissão, Renovação e Revogação

Geração Segura

// Geração com claims essenciais
const token = jwt.sign(
  {
    sub: user.id,
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + (60 * 15), // 15 minutos
    jti: uuid.v4(), // ID único do token
    iss: 'https://auth.meudominio.com',
    aud: 'https://api.meudominio.com'
  },
  chavePrivada,
  { algorithm: 'RS256' }
);

Estratégia de Tokens Duplos

  • Access token: curta duração (15 minutos), enviado em cada requisição
  • Refresh token: longa duração (7 dias), usado apenas para obter novos access tokens
// Fluxo de renovação
POST /auth/refresh
{
  "refreshToken": "eyJhbGciOiJSUzI1NiIs..."
}

Revogação

  • Blacklists: armazenar tokens revogados (jti) em Redis/banco
  • Short expiration: minimizar o impacto de tokens comprometidos
  • Token rotation: invalidar refresh token ao usar, emitindo um novo

6. Boas Práticas de Armazenamento e Transmissão

Onde Armazenar no Cliente

Método Segurança Recomendação
localStorage Vulnerável a XSS Evitar para tokens sensíveis
Session cookies (HttpOnly, Secure, SameSite) Mais seguro Preferível para access tokens
// Configuração segura de cookie
Set-Cookie: access_token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/api; Max-Age=900

Proteção contra XSS e CSRF

  • XSS: usar cookies HttpOnly impede acesso via JavaScript malicioso
  • CSRF: usar SameSite=Strict/Lax e tokens anti-CSRF

Informações Sensíveis

Nunca inclua no payload:
- Senhas
- Números de cartão de crédito
- Dados biométricos
- Informações pessoais identificáveis desnecessárias

7. Monitoramento e Prevenção de Ataques Comuns

Detecção de Tokens Manipulados

// Log de eventos de token
{
  "event": "token_validation_failed",
  "reason": "signature_mismatch",
  "jti": "abc-123",
  "timestamp": "2024-01-15T10:30:00Z",
  "ip": "192.168.1.100"
}

Proteção contra Replay

Use jti (JWT ID) único e armazene tokens já utilizados:

// Verificação de nonce
if (tokenUsado.includes(decoded.jti)) {
  throw new Error('Token já utilizado');
}

Prevenção de Força Bruta

  • Rate limiting nas rotas de autenticação
  • Monitoramento de tentativas de validação falhas
  • Bloqueio temporário após múltiplas falhas

Referências