JWT e Base64URL: Como Funciona a Autenticação com JSON Web Tokens
JWT é o padrão de autenticação stateless mais usado em SPAs, mobile e microserviços. Por baixo dos panos, ele usa Base64URL — o que explica por que qualquer pessoa decodifica o payload sem a chave. Veja anatomia completa, escolha de algoritmo, boas práticas e os erros mais comuns.
Por Vitor Morais
Fundador do MochaLabz ·
Decodifique header e payload de JWT
Cole o token e veja o conteúdo decodificado em segundos. 100% no navegador, sem enviar dados.
Usar codificador Base64 →JWT (JSON Web Token, RFC 7519) é o padrão de autenticação stateless mais usado na web em 2026. Está em praticamente todo SPA, app mobile, OAuth 2.0 e OpenID Connect. A magia que torna ele compacto e portável é o uso de Base64URL para codificar header e payload — o que tem implicações importantes de segurança que todo dev precisa entender. Este guia cobre anatomia completa, escolha de algoritmo, decodificação manual em qualquer linguagem, boas práticas e os erros mais comuns.
Anatomia de um JWT
Um JWT é uma string com três partes separadas por ponto (.):
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQW5hIn0.fH3...
┌─────────────────┐ ┌──────────────────────────────┐ ┌───────────┐
│ Header │.│ Payload │.│ Signature │
│ algoritmo + │ │ claims (sub, exp, iat, ...) │ │ (HMAC ou │
│ tipo │ │ JSON em B64URL │ │ RSA) │
└─────────────────┘ └──────────────────────────────┘ └───────────┘
Base64URL Base64URL Base64URLAs três partes são todas em Base64URL. Header e payload são apenas codificados — qualquer pessoa decodifica e lê. A signature é o que garante integridade. Para entender melhor a base do encoding, veja o que é Base64.
Decodificando o header
O header diz qual algoritmo de assinatura foi usado e o tipo do token. Quase sempre é JSON minúsculo:
Header em Base64URL:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Decodifica para:
{ "alg": "HS256", "typ": "JWT" }
Campos comuns:
alg → algoritmo de assinatura (HS256, RS256, ES256, ...)
typ → tipo do token (sempre "JWT")
kid → key ID, identifica qual chave usar (em rotação de chaves)Decodificando o payload (claims)
O payload contém as claims — afirmações sobre o usuário e o token. JWT define claims padrão e permite custom:
| Critério | Significado |
|---|---|
| iss (issuer) | Quem emitiu o token (ex: 'auth.meusite.com') |
| sub (subject) | Sobre quem é o token (ex: ID do usuário) |
| aud (audience) | Para qual serviço o token vale (ex: 'api.meusite.com') |
| exp (expiration) | Timestamp Unix de expiração |
| nbf (not before) | Timestamp antes do qual não é válido |
| iat (issued at) | Timestamp de quando foi emitido |
| jti (JWT ID) | ID único do token (útil para revogação) |
Payload exemplo:
{
"sub": "user_018f4e8e-3a2b-7000",
"name": "Ana Silva",
"email": "ana@example.com",
"roles": ["user", "admin"],
"iss": "auth.meusite.com",
"aud": "api.meusite.com",
"iat": 1718812800,
"exp": 1718816400 // 1h de validade
}Payload é PÚBLICO
Tudo no payload pode ser lido por qualquer pessoa que tenha o token. NÃO coloque senha, número de cartão, CPF de terceiros, ou qualquer dado sensível. Use apenas informação necessária para autorização (id, roles, escopos).
Base64 vs Base64URL: a única diferença que importa
Base64URL é variação do Base64 padrão otimizada para uso em URLs e headers HTTP. As mudanças:
| Critério | Base64 padrão | Base64URL |
|---|---|---|
| Caractere índice 62 | + | - |
| Caractere índice 63 | / | _ |
| Padding com = | Obrigatório | Geralmente omitido |
| Seguro em URL | Não — precisa escapar + e / | Sim, direto |
| Onde aparece | E-mail (MIME), Basic Auth | JWT, OAuth, JOSE em geral |
Decodificando JWT em JavaScript (sem biblioteca)
function base64UrlDecode(str: string): string {
// Base64URL → Base64 padrão
let b64 = str.replace(/-/g, '+').replace(/_/g, '/');
// Recoloca padding = se faltar
while (b64.length % 4 !== 0) b64 += '=';
// Decodifica
return atob(b64);
}
function decodeJWT(token: string) {
const [headerB64, payloadB64, signature] = token.split('.');
return {
header: JSON.parse(base64UrlDecode(headerB64)),
payload: JSON.parse(base64UrlDecode(payloadB64)),
signature, // ainda em Base64URL, não decodificável como JSON
};
}
// Uso
const token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.fH3...';
const decoded = decodeJWT(token);
console.log(decoded.header); // { alg: 'HS256' }
console.log(decoded.payload); // { sub: '123' }Decodificando JWT em Python
import base64
import json
def decode_jwt_part(part: str) -> dict:
# Recoloca padding se faltar
padded = part + '=' * (-len(part) % 4)
decoded = base64.urlsafe_b64decode(padded)
return json.loads(decoded)
def decode_jwt(token: str) -> dict:
header_b64, payload_b64, signature = token.split('.')
return {
'header': decode_jwt_part(header_b64),
'payload': decode_jwt_part(payload_b64),
'signature': signature,
}
# Uso
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.fH3..."
print(decode_jwt(token))A signature: a parte que importa para segurança
Header e payload são apenas codificação. A signature é o que garante que o token não foi alterado. Para HS256:
signature = HMAC-SHA256(
header_base64url + "." + payload_base64url,
CHAVE_SECRETA
)
// Qualquer mudança em header ou payload muda a signature.
// O servidor recalcula no recebimento. Se diferente → token inválido.
// Para RS256/ES256 a lógica é a mesma, mas usando criptografia
// assimétrica (chave privada gera, chave pública valida).HS256 vs RS256 vs ES256: qual escolher
| Critério | Tipo de chave | Tamanho | Quando usar |
|---|---|---|---|
| HS256 (HMAC + SHA-256) | Simétrica (uma só chave) | 32+ bytes | Single service ou serviço de auth dedicado |
| HS384 / HS512 | Simétrica | 48 / 64 bytes | Quando 256 bits não basta (raríssimo) |
| RS256 (RSA + SHA-256) | Assimétrica | Privada 2048 bits | Vários serviços validam tokens emitidos por um Auth Service |
| ES256 (ECDSA + P-256) | Assimétrica (curva elíptica) | Privada ~256 bits | Recomendado para projetos novos — chaves menores, mais rápido que RSA |
| EdDSA (Ed25519) | Assimétrica (curva Edwards) | Privada 32 bytes | Algoritmo moderno; suporte ainda mais raro |
| none (sem assinatura) | — | — | NUNCA USE — vulnerabilidade clássica |
Recomendação 2026
Para projetos novos: ES256 (chaves pequenas, assinatura rápida, fácil rotação). Para serviços que precisam compatibilidade ampla com libs antigas: RS256. Para sistemas internos simples: HS256 com chave de pelo menos 32 bytes aleatórios (cripto-secure).
Implementação completa em Node.js
// npm install jsonwebtoken
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET!; // 32+ bytes aleatórios
// Emitir token (no login bem-sucedido)
function emitirToken(usuario: { id: string; roles: string[] }) {
return jwt.sign(
{
sub: usuario.id,
roles: usuario.roles,
},
SECRET,
{
algorithm: 'HS256',
issuer: 'auth.meusite.com',
audience: 'api.meusite.com',
expiresIn: '1h',
},
);
}
// Validar token (em cada request autenticada)
function validarToken(token: string) {
try {
const decoded = jwt.verify(token, SECRET, {
algorithms: ['HS256'], // SEMPRE especifique (evita "alg: none")
issuer: 'auth.meusite.com',
audience: 'api.meusite.com',
});
return { ok: true, claims: decoded };
} catch (err) {
return { ok: false, error: err.message };
}
}Implementação com chaves assimétricas (RS256)
import jwt from 'jsonwebtoken';
import { readFileSync } from 'fs';
const PRIVATE_KEY = readFileSync('./keys/private.pem');
const PUBLIC_KEY = readFileSync('./keys/public.pem');
// Auth Service: emite com chave PRIVADA
const token = jwt.sign(payload, PRIVATE_KEY, {
algorithm: 'RS256',
expiresIn: '1h',
});
// Microserviços: validam com chave PÚBLICA
// (não precisam saber a chave privada)
const decoded = jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'],
});Onde armazenar o JWT no cliente
| Critério | Vulnerabilidades |
|---|---|
| Cookie httpOnly + Secure + SameSite=Strict | ✅ Imune a XSS · ⚠️ CSRF (mitigado por SameSite) |
| localStorage / sessionStorage | ❌ XSS lê o token instantaneamente |
| Memória (variável JS) | ✅ XSS persistente não pega · ❌ Perde no refresh |
| Padrão híbrido (refresh em cookie + access em memória) | ✅ Melhor compromisso em SPAs |
Boas práticas de segurança com JWT
- Sempre HTTPS: JWT em HTTP é interceptável.
- Especifique algorithms na validação: evita ataque clássico do
alg: none. - Use exp curto (15 min a 1h) com refresh token de longa duração.
- Inclua iss e aud e valide no servidor — evita reutilização cruzada entre serviços.
- Rotação de chaves: use
kidno header para identificar a chave atual; mantenha chave antiga válida durante a transição. - Revogação: mantenha lista de tokens revogados (Redis) ou refresh server-side com access curto.
- Não armazene senha hasheada no payload nem qualquer dado sensível.
- Cookie httpOnly em vez de localStorage.
Os 8 erros que destroem a segurança de quem usa JWT
Lista negra clássica
- alg: none accepted: alguns libs aceitam tokens com algoritmo “none” — atacante remove signature e o token passa. Sempre especifique
algorithms: ['HS256']. - Confusão HS256/RS256: servidor configurado para RS256 mas aceita HS256 com a chave pública como segredo HMAC — atacante assina sozinho. Especifique algorithms.
- Chave fraca em HS256: chaves curtas (4–8 bytes) são quebradas por brute force em horas. Use 32+ bytes aleatórios.
- JWT em URL: token vaza em logs do servidor, referrer headers, histórico do browser.
- Payload com dados sensíveis: qualquer um decodifica.
- localStorage para token: XSS = sessão comprometida.
- Sem expiração: token válido para sempre é bomba-relógio.
- Sem mecanismo de revogação: em incidente, não dá para invalidar tokens emitidos.
JWT vs sessão server-side: quando cada um vence
| Critério | JWT | Sessão server-side |
|---|---|---|
| Estado no servidor | Nenhum | Sim (Redis, banco) |
| Revogação imediata | Difícil (precisa blacklist) | Fácil (delete da sessão) |
| Performance em validação | Local, rápido | Lookup externo a cada req |
| Tamanho do token | Maior (claims + signature) | Pequeno (session ID) |
| Bom para microserviços | ✅ | ❌ (lookup central) |
| Bom para apps server-rendered | ⚠️ Ok, mas overkill | ✅ |
| Bom para mobile / SPA | ✅ | ⚠️ Ok |
Refresh tokens: o padrão moderno
Combine access token JWT curto (15 min) com refresh token mais longo, server-side:
Login bem-sucedido:
→ Servidor emite:
- access_token (JWT, exp = 15 min, no body da resposta)
- refresh_token (random 32 bytes, exp = 30 dias,
em cookie httpOnly + tabela no banco)
A cada request da API:
→ cliente envia access_token no Authorization header
→ servidor valida assinatura (rápido, stateless)
Quando access_token expira:
→ cliente faz POST /auth/refresh com cookie
→ servidor checa refresh_token contra a tabela
→ se válido: emite novo access_token
→ se revogado/expirado: força login
Em logout / incidente:
→ DELETE da linha do refresh_token
→ access_token ainda vale por até 15 min
→ após isso, cliente é forçado a logarChecklist do JWT bem implementado
- ✅ Algoritmo definido: ES256 (ideal) ou RS256 / HS256.
- ✅ Em HS256, chave de 32+ bytes aleatórios (não senha humana).
- ✅
algorithms: [...]especificado na validação. - ✅ Claims iss, aud, sub, exp, iat sempre presentes.
- ✅ exp curto (15 min – 1h) + refresh token mais longo.
- ✅ Token armazenado em cookie httpOnly + Secure + SameSite=Strict.
- ✅ Sempre via HTTPS. Nunca em URL.
- ✅ Payload sem dados sensíveis (senha, cartão, CPF de terceiros).
- ✅ Mecanismo de revogação (blacklist Redis ou refresh server-side).
- ✅ Rotação de chaves via campo
kidno header. - ✅ Logging de eventos de autenticação para auditoria.
- ✅ Testes específicos contra os 8 ataques clássicos.
Perguntas frequentes
O que é JWT (JSON Web Token)?+
JWT é um padrão aberto (RFC 7519) para representar claims (afirmações) de forma compacta, segura e auto-contida entre duas partes. Em autenticação web, o servidor emite um JWT após login; o cliente envia esse token em cada requisição; o servidor valida a assinatura e confia nos dados internos sem precisar consultar a base. É a base de OAuth 2.0, OpenID Connect e a maioria dos sistemas SPA/mobile modernos.
Por que JWT usa Base64URL e não Base64 padrão?+
Porque tokens trafegam em URLs (query strings, redirects OAuth) e headers HTTP, onde os caracteres + e / do Base64 padrão precisariam ser escapados. Base64URL substitui + por -, / por _ e omite o padding =, deixando o token URL-safe nativamente. O algoritmo de codificação subjacente é o mesmo, só muda o alfabeto final.
Posso decodificar um JWT sem ter a chave secreta?+
Sim, e isso é o ponto mais mal-entendido sobre JWT. Header e payload são apenas Base64URL — qualquer pessoa decodifica em segundos. A chave secreta serve só para gerar e validar a assinatura (terceira parte do token), garantindo que o token não foi adulterado. Por isso: nunca coloque senha, número de cartão ou qualquer dado sensível no payload.
Qual a diferença entre HS256, RS256 e ES256?+
HS256 é HMAC com SHA-256 — usa chave SIMÉTRICA (mesma para gerar e validar). Bom para single-service. RS256 é RSA-SHA-256 — usa chave PÚBLICA/PRIVADA (privada gera, pública valida). Ideal quando vários serviços precisam validar tokens emitidos por um único Auth Service. ES256 é ECDSA com curva P-256 — também assimétrico, com chaves muito menores que RSA. Em 2026, ES256 é a escolha moderna recomendada.
JWT é melhor que session cookie tradicional?+
Depende. JWT brilha em sistemas distribuídos (vários serviços validam sem consultar uma sessão central) e em mobile/SPA (token compacto, fácil de armazenar). Session cookies brilham em apps server-rendered (revogação imediata, controle total no servidor, integração nativa com browsers). Em 2026, é comum usar híbrido: refresh token (server-side session) + access token (JWT curto).
Onde armazenar JWT no navegador?+
Cookie httpOnly + Secure + SameSite=Strict é a opção mais segura — não acessível por JavaScript, imune a XSS. localStorage é vulnerável a XSS (qualquer script malicioso lê o token). sessionStorage tem o mesmo problema. Memória pura (variável JS) protege de XSS persistente, mas perde o login a cada refresh. Padrão atual: access token em memória + refresh token em cookie httpOnly.
Quanto tempo um JWT deve durar?+
Access token: curto (15 a 60 minutos). Refresh token: longo (7 a 30 dias). Padrão OAuth 2.0. Quando o access expira, o cliente usa o refresh para pegar novo access sem login. Tokens longos demais (dias/meses) são vetor de risco — se vazados, o atacante tem acesso por todo o período. Tokens curtos demais geram fricção. O equilíbrio depende da criticidade da aplicação.
Posso revogar um JWT antes de expirar?+
Por design, não — JWT é stateless. Para revogação real, você precisa quebrar essa promessa: manter blacklist de tokens revogados (Redis, banco), checada a cada request, ou usar refresh tokens server-side e access tokens muito curtos. A combinação mais comum em produção: access token de 15 minutos + lista de revogação de refresh tokens no servidor.
Continue lendo
bcrypt vs Argon2 vs scrypt vs PBKDF2: Qual Usar para Senhas (2026)
Comparativo técnico OWASP-aligned entre bcrypt, Argon2id, scrypt e PBKDF2 para hashing de senhas em 2026. Recomendações, parâmetros corretos por hardware, exemplos em Node.js/Python/Go/PHP e estratégia de migração.
O que é Base64? Como Funciona, Para que Serve e Como Usar
Guia completo de Base64: o que é, como o algoritmo converte binário em texto, onde aparece (JWT, e-mail, imagens), variantes (Base64URL), código em JS, Python e Go, e por que NÃO é criptografia.
Base64 para Imagens em HTML e CSS: Quando Vale (e Quando Evita) — 2026
Embutir imagens como Base64 (Data URLs) elimina requisições HTTP mas aumenta tamanho do HTML/CSS e quebra cache. Análise completa, casos de uso reais (LQIP, ícones SVG, e-mail), benchmarks e alternativas modernas.
LGPD para Sites: Checklist Completo de Adequação (2026)
Guia LGPD para sites brasileiros: 30+ itens de checklist, bases legais explicadas, multas reais aplicadas pela ANPD, prioridade de implementação por fase e ferramentas recomendadas.