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?
- Estrutura do JWT: Cabeçalho, Payload e Assinatura
- Claims Padrão (RFC 7519)
- Como Funciona a Autenticação JWT
- Exemplos de Implementação
- Melhores Práticas de Segurança
- Tokens de Atualização e Rotação de Tokens
- JWT vs Autenticação Baseada em Sessão
- Erros Comuns a Evitar
- Onde Armazenar JWTs
- Perguntas Frequentes
- Artigos Relacionados
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:
- Sistemas de Single Sign-On (SSO) onde usuários autenticam uma vez e acessam múltiplos serviços
- Arquiteturas de microsserviços onde serviços precisam verificar identidade do usuário sem armazenamento de sessão compartilhado
- Aplicações móveis onde manter sessões no servidor é impraticável
- Autenticação de API onde verificação stateless melhora escalabilidade
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:
- HS256 (HMAC com SHA-256): Algoritmo simétrico usando um segredo compartilhado
- RS256 (RSA com SHA-256): Algoritmo assimétrico usando pares de chaves pública/privada
- ES256 (ECDSA com SHA-256): Algoritmo assimétrico com tamanhos de chave menores
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:
roleouroles: Permissões do usuário (ex:"admin","user")email: Endereço de email do usuárioname: Nome de exibição do usuárioscope: Escopos OAuth 2.0 para acesso à APItenant_id: Identificador de aplicação multi-tenant
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:
- Login do Usuário: Usuário envia credenciais (nome de usuário + senha) para seu endpoint de autenticação
- Verificação de Credenciais: Servidor valida credenciais contra seu banco de dados de usuários
- Geração de Token: Servidor cria um JWT contendo claims do usuário e o assina com uma chave secreta
- Entrega do Token: Servidor retorna o JWT ao cliente (tipicamente no corpo da resposta ou como cookie httpOnly)
- Armazenamento do Token: Cliente armazena o JWT com segurança (mais sobre isso na seção Opções de Armazenamento)
- Requisições Autenticadas: Cliente inclui o JWT no cabeçalho
Authorizationpara requisições subsequentes - Verificação do Token: Servidor verifica a assinatura e checa expiração—nenhuma consulta ao banco de dados necessária
- 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