Artigo Build·Desenvolvimento·13 min de leitura

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.

Vitor Morais

Por Vitor Morais

Fundador do MochaLabz ·

64

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 Base64URL

As 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:

Claims padrão JWT (RFC 7519)
CritérioSignificado
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:

Base64 padrão vs Base64URL
CritérioBase64 padrãoBase64URL
Caractere índice 62+-
Caractere índice 63/_
Padding com =ObrigatórioGeralmente omitido
Seguro em URLNão — precisa escapar + e /Sim, direto
Onde apareceE-mail (MIME), Basic AuthJWT, 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

Algoritmos de assinatura JWT comparados
CritérioTipo de chaveTamanhoQuando usar
HS256 (HMAC + SHA-256)Simétrica (uma só chave)32+ bytesSingle service ou serviço de auth dedicado
HS384 / HS512Simétrica48 / 64 bytesQuando 256 bits não basta (raríssimo)
RS256 (RSA + SHA-256)AssimétricaPrivada 2048 bitsVários serviços validam tokens emitidos por um Auth Service
ES256 (ECDSA + P-256)Assimétrica (curva elíptica)Privada ~256 bitsRecomendado para projetos novos — chaves menores, mais rápido que RSA
EdDSA (Ed25519)Assimétrica (curva Edwards)Privada 32 bytesAlgoritmo 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

Estratégias de armazenamento de JWT no navegador
CritérioVulnerabilidades
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 kid no 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

JWT (stateless) vs sessão server-side (stateful)
CritérioJWTSessão server-side
Estado no servidorNenhumSim (Redis, banco)
Revogação imediataDifícil (precisa blacklist)Fácil (delete da sessão)
Performance em validaçãoLocal, rápidoLookup externo a cada req
Tamanho do tokenMaior (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 logar

Checklist 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 kid no 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.

#jwt#base64url#json web token#autenticação#segurança#javascript#hs256#rs256#es256#oauth

Continue lendo