Tokens JWT Explicados: Estrutura, Segurança e Melhores Práticas

· 12 min de leitura

JSON Web Tokens (JWTs) tornaram-se o padrão de facto para autenticação stateless em aplicações web modernas. Seja construindo uma API REST, uma arquitetura de microsserviços ou uma aplicação de página única, entender como JWTs funcionam é essencial para implementar autenticação segura e escalável.

Este guia abrangente detalha tudo o que você precisa saber sobre JWTs—desde sua estrutura interna até práticas de segurança prontas para produção. Ao final, você entenderá não apenas como usar JWTs, mas quando usá-los e como evitar armadilhas comuns que levam a vulnerabilidades de segurança.

Índice

O que é um JWT?

Um JSON Web Token (JWT) é um meio compacto e seguro para URL de representar claims a serem transferidas entre duas partes. As claims em um JWT são codificadas como um objeto JSON que é assinado digitalmente usando JSON Web Signature (JWS).

Pense em um JWT como um contêiner à prova de adulteração para informações do usuário. Quando um usuário faz login, seu servidor cria um JWT contendo o ID do usuário, funções e outros dados relevantes. Este token é então enviado ao cliente, que o inclui em cada requisição subsequente. O servidor pode verificar a autenticidade do token sem consultar um banco de dados, tornando JWTs ideais para arquiteturas stateless e escaláveis.

JWTs são definidos pela RFC 7519 e são amplamente suportados em linguagens de programação e frameworks. São particularmente populares em:

Dica rápida: Use nosso Decodificador JWT para inspecionar e depurar qualquer token JWT instantaneamente. É completamente client-side, então seus tokens nunca saem do seu navegador.

Estrutura do JWT: Cabeçalho, Payload e Assinatura

Um JWT consiste em três partes codificadas em Base64URL separadas por pontos (.). Veja como um JWT real se parece:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Detalhando:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9           ← Cabeçalho
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ  ← Payload
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c    ← Assinatura
Parte Contém Exemplo (decodificado)
Cabeçalho Algoritmo + tipo de token {"alg": "HS256", "typ": "JWT"}
Payload Claims (dados do usuário) {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
Assinatura Hash de verificação HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

O Cabeçalho

O cabeçalho normalmente consiste em duas partes: o tipo de token (typ) e o algoritmo de assinatura (alg). Algoritmos comuns incluem:

O Payload

O payload contém as claims—declarações sobre uma entidade (tipicamente o usuário) e metadados adicionais. Claims não são criptografadas, apenas codificadas, então nunca coloque informações sensíveis como senhas em um payload JWT.

A Assinatura

A assinatura garante que o token não foi adulterado. É criada pegando o cabeçalho codificado, payload codificado, uma chave secreta e aplicando o algoritmo especificado no cabeçalho. Se alguém modificar o cabeçalho ou payload, a verificação da assinatura falhará.

Dica profissional: Gere JWTs de teste com claims personalizadas usando nossa ferramenta Gerador JWT. Perfeito para cenários de desenvolvimento e teste.

Claims Padrão (RFC 7519)

A RFC 7519 define várias claims padrão que fornecem interoperabilidade entre diferentes implementações JWT. Embora essas claims sejam opcionais, usá-las corretamente melhora segurança e compatibilidade.

Claim Nome Propósito Exemplo
iss Emissor Identifica quem criou o token "https://auth.example.com"
sub Assunto Identifica sobre quem é o token (geralmente ID do usuário) "user_12345"
aud Audiência Identifica para quem o token é destinado "https://api.example.com"
exp Expiração Quando o token expira (timestamp Unix) 1735689600
nbf Não Antes Token não é válido antes deste horário 1735603200
iat Emitido Em Quando o token foi criado 1735603200
jti ID JWT Identificador único para o token (útil para revogação) "a1b2c3d4-e5f6"

Claims Personalizadas

Além das claims padrão, você pode adicionar claims personalizadas específicas para sua aplicação. Exemplos comuns incluem:

Mantenha seu payload pequeno—cada byte adiciona sobrecarga de rede. Inclua apenas as claims necessárias para decisões de autorização. Se precisar de dados adicionais do usuário, busque-os do seu banco de dados usando a claim sub como chave de busca.

Como Funciona a Autenticação JWT

Entender o fluxo completo de autenticação ajuda você a implementar JWTs corretamente e com segurança. Aqui está a sequência típica:

  1. Login do Usuário: Usuário envia credenciais (nome de usuário + senha) para seu endpoint de autenticação
  2. Verificação de Credenciais: Servidor valida credenciais contra seu banco de dados de usuários
  3. Geração de Token: Servidor cria um JWT contendo claims do usuário e o assina com uma chave secreta
  4. Entrega do Token: Servidor retorna o JWT ao cliente (tipicamente no corpo da resposta ou como cookie httpOnly)
  5. Armazenamento do Token: Cliente armazena o JWT com segurança (mais sobre isso na seção Opções de Armazenamento)
  6. Requisições Autenticadas: Cliente inclui o JWT no cabeçalho Authorization para requisições subsequentes
  7. Verificação do Token: Servidor verifica a assinatura e checa expiração—nenhuma consulta ao banco de dados necessária
  8. Acesso Concedido: Se válido, servidor processa a requisição usando claims do token

Aqui está uma representação visual:

┌────────┐                                  ┌────────┐
│ Cliente│                                  │Servidor│
└───┬────┘                                  └───┬────┘
    │                                           │
    │  POST /login {username, password}         │
    │──────────────────────────────────────────>│
    │                                           │
    │                                    [Verificar credenciais]
    │                                    [Gerar JWT]
    │                                           │
    │  200 OK {token: "eyJhbG..."}              │
    │<──────────────────────────────────────────│
    │                                           │
[Armazenar token]                               │
    │                                           │
    │  GET /api/profile                         │
    │  Authorization: Bearer eyJhbG...          │
    │──────────────────────────────────────────>│
    │                                           │
    │                                    [Verificar assinatura]
    │                                    [Checar expiração]
    │                                    [Extrair claims]
    │                                           │
    │  200 OK {user data}                       │
    │<──────────────────────────────────────────│
    │                                           │

Dica profissional: A beleza dos JWTs é que o passo 7 (verificação do token) não requer uma consulta ao banco de dados. O servidor pode verificar autenticidade e extrair informações do usuário diretamente do token, tornando sua API altamente escalável.

Exemplos de Implementação

Vamos ver implementações práticas em linguagens de programação e frameworks populares.

Node.js com jsonwebtoken

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

// Endpoint de login
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  
  // Encontrar usuário no banco de dados
  const user = await User.findOne({ username });
  if (!user) {
    return res.status(401).json({ error: 'Credenciais inválidas' });
  }
  
  // Verificar senha
  const validPassword = await bcrypt.compare(password, user.passwordHash);
  if (!validPassword) {
    return res.status(401).json({ error: 'Credenciais inválidas' });
  }
  
  // Criar JWT
  const token = jwt.sign(
    { 
      sub: user.id,
      email: user.email,
      role: user.role 
    },
    process.env.JWT_SECRET,
    { 
      expiresIn: '15m',
      issuer: 'https://api.example.com',
      audience: 'https://example.com'
    }
  );
  
  res.json({ token });
});

// Middleware de rota protegida
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
  
  if (!token) {
    return res.status(401).json({ error: 'Nenhum token fornecido' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(403).json({ error: 'Token inválido ou expirado' });
  }
};

// Usar o middleware
app.get('/api/profile', authenticateToken, (req, res) => {
  res.json({ userId: req.user.sub, email: req.user.email });
});

Python com PyJWT

import jwt
import datetime
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET_KEY = 'your-secret-key'

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    # Verificar credenciais (simplificado)
    user = verify_credentials(username, password)
    if not user:
        return jsonify({'error': 'Credenciais inválidas'}), 401
    
    # Criar JWT
    payload = {
        'sub': user['id'],
        'email': user['email'],
        'role': user['role'],
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15),
        'iat': datetime.datetime.utcnow()
    }
    
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return jsonify({'token': token})

def token_required(f):
    def decorator(*args, **kwargs):
        token = request.headers.get('Authorization')
        
        if not token:
            return jsonify({'error': 'Nenhum token fornecido'}), 401
        
        try:
            # Remover prefixo 'Bearer '
            token = token.split(' ')[1]
            decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
            request.user = decoded
        except jwt.ExpiredSignatureError:
            return jsonify({'error': 'Token expirado'}), 403
        except jwt.InvalidTokenError:
            return jsonify({'error': 'Token inválido'}), 403
        
        return f(*args, **kwargs)
    
    return decorator

@app.route('/api/profile')
@token_required
def profile():
    return jsonify({'userId': request.user['sub']})

Go com golang-jwt

package main

import (
    "time"
    "github.com/golang-jwt/jwt/v5"
)

type Claims struct {
    UserID string `json:"sub"`
    Email  string `json:"email"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

func generateToken(userID, email, role string) (string, error) {
    claims := Claims{
        UserID: userID,
        Email:  emai