Autenticação JWT: Como Funcionam os JSON Web Tokens

· 12 min de leitura

Índice

O que é um JSON Web Token?

Um JSON Web Token (JWT) é uma forma compacta e segura para URL de representar declarações entre duas partes. Os JWTs se tornaram o padrão de fato para autenticação e autorização em aplicações web modernas, APIs e arquiteturas de microsserviços.

Quando um usuário faz login, o servidor gera um JWT contendo informações do usuário e o envia ao cliente. O cliente então inclui este token em requisições subsequentes para provar sua identidade. Pense nisso como um passaporte digital que contém suas credenciais e pode ser verificado sem precisar consultar a autoridade emissora toda vez.

Diferentemente da autenticação tradicional baseada em sessão, onde o servidor mantém o estado da sessão na memória ou em um banco de dados, os JWTs são stateless. Todas as informações necessárias para verificar o token estão contidas no próprio token. Esta decisão arquitetural traz várias vantagens:

Você pode inspecionar e decodificar qualquer JWT usando nossa ferramenta de Decodificador JWT para ver seu conteúdo sem precisar escrever nenhum código. Isso é particularmente útil ao depurar problemas de autenticação ou entender quais dados seus tokens contêm.

Dica profissional: JWTs são assinados, não criptografados por padrão. Qualquer pessoa pode decodificar e ler o conteúdo de um JWT. Nunca armazene informações sensíveis como senhas, números de cartão de crédito ou números de seguro social em payloads JWT.

Estrutura do JWT Explicada

Um JWT consiste em três partes distintas separadas por pontos (.): o Cabeçalho, o Payload e a Assinatura. Veja como um JWT real se parece:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Vamos detalhar cada componente para entender como eles trabalham juntos para criar um token seguro e verificável.

1. Cabeçalho

O cabeçalho normalmente contém dois campos críticos que definem como o token deve ser processado:

{
  "alg": "HS256",
  "typ": "JWT"
}

Este objeto JSON é então codificado em Base64Url para formar a primeira parte do JWT. A codificação o torna seguro para URL e compacto para transmissão em cabeçalhos HTTP.

2. Payload

O payload contém declarações — afirmações sobre o usuário e metadados adicionais. É aqui que você armazena os dados reais que deseja transmitir:

{
  "sub": "1234567890",
  "name": "John Doe",
  "email": "[email protected]",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

As declarações são divididas em três categorias:

Declarações registradas (padronizadas e recomendadas):

Declarações públicas são declarações personalizadas que devem ser definidas no Registro IANA de JSON Web Token ou usar nomes resistentes a colisão (como URLs).

Declarações privadas são declarações personalizadas acordadas entre as partes, como role, permissions ou department.

3. Assinatura

A assinatura é o que torna os JWTs seguros e à prova de adulteração. Ela é criada pegando o cabeçalho codificado, o payload codificado, uma chave secreta e aplicando o algoritmo especificado no cabeçalho:

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

A assinatura serve a dois propósitos críticos:

  1. Verificação de integridade: Garante que o token não foi modificado desde que foi emitido
  2. Autenticação: Prova que o token foi criado por alguém com acesso à chave secreta

Se alguém tentar modificar o cabeçalho ou payload, a verificação da assinatura falhará e o token será rejeitado.

Como Funciona a Autenticação JWT

Entender o fluxo completo de autenticação é essencial para implementar JWTs corretamente. Aqui está o processo passo a passo:

Passo 1: Login do Usuário

O usuário envia suas credenciais (nome de usuário e senha) para o endpoint de autenticação. O servidor valida essas credenciais contra o banco de dados.

Passo 2: Geração do Token

Se as credenciais forem válidas, o servidor gera um JWT contendo informações do usuário e declarações. O servidor assina o token usando uma chave secreta (para algoritmos simétricos) ou uma chave privada (para algoritmos assimétricos).

Passo 3: Entrega do Token

O servidor envia o JWT de volta ao cliente, normalmente no corpo da resposta. O cliente armazena este token para requisições futuras.

Passo 4: Requisições Autenticadas

Para requisições subsequentes, o cliente inclui o JWT no cabeçalho Authorization usando o esquema Bearer:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Passo 5: Verificação do Token

O servidor recebe a requisição, extrai o JWT do cabeçalho e verifica a assinatura usando a chave secreta ou pública. Se válido, o servidor extrai as informações do usuário do payload e processa a requisição.

Passo 6: Expiração do Token

Quando o token expira, o cliente deve obter um novo token, seja reautenticando ou usando um token de atualização (coberto na próxima seção).

Dica rápida: Sempre valide a declaração exp no lado do servidor, mesmo que você verifique a expiração no cliente. Verificações do lado do cliente podem ser contornadas, mas a validação do lado do servidor é autoritativa.

Tokens de Acesso vs Tokens de Atualização

Um sistema robusto de autenticação JWT normalmente usa dois tipos de tokens trabalhando juntos: tokens de acesso e tokens de atualização. Entender a distinção é crucial para construir aplicações seguras.

Tokens de Acesso

Tokens de acesso são JWTs de curta duração (normalmente 15 minutos a 1 hora) que concedem acesso a recursos protegidos. Eles contêm identidade do usuário e permissões necessárias para autorizar requisições.

Características:

Tokens de Atualização

Tokens de atualização são tokens de longa duração (dias a meses) usados exclusivamente para obter novos tokens de acesso. Eles são armazenados com segurança e enviados apenas para o endpoint de atualização de token.

Características:

Por Que Usar Ambos?

Esta abordagem de token duplo equilibra segurança e experiência do usuário:

Aspecto Apenas Token de Acesso Acesso + Tokens de Atualização
Segurança Menor (tokens de longa duração em risco) Maior (tokens de acesso de curta duração)
Experiência do Usuário Melhor (sem reautenticação) Melhor (atualização de token sem interrupção)
Revogação Difícil (stateless) Possível (tokens de atualização no BD)
Sobrecarga de Rede Menor Ligeiramente maior (requisições de atualização)
Complexidade de Implementação Simples Moderada

Fluxo de Atualização de Token

  1. Cliente detecta que o token de acesso expirou ou está prestes a expirar
  2. Cliente envia token de atualização para o endpoint /auth/refresh
  3. Servidor valida token de atualização contra o banco de dados
  4. Servidor gera novo token de acesso (e opcionalmente novo token de atualização)
  5. Cliente recebe e armazena novos tokens
  6. Cliente tenta novamente a requisição original com novo token de acesso

Dica profissional: Implemente rotação de token de atualização — emita um novo token de atualização cada vez que um for usado e invalide o antigo. Isso limita o dano se um token de atualização for comprometido.

Guia de Implementação em Node.js

Vamos construir um sistema completo de autenticação JWT usando Node.js, Express e a biblioteca jsonwebtoken. Esta implementação inclui tokens de acesso e de atualização.

Instalação

npm install express jsonwebtoken bcrypt dotenv

Configuração de Ambiente

Crie um arquivo .env com suas chaves secretas:

ACCESS_TOKEN_SECRET=your-super-secret-access-key-change-this
REFRESH_TOKEN_SECRET=your-super-secret-refresh-key-change-this
ACCESS_TOKEN_EXPIRY=15m
REFRESH_TOKEN_EXPIRY=7d

Geração de Token

const jwt = require('jsonwebtoken');

function generateAccessToken(user) {
  return jwt.sign(
    { 
      userId: user.id,
      email: user.email,
      role: user.role 
    },
    process.env.ACCESS_TOKEN_SECRET,
    { expiresIn: process.env.ACCESS_TOKEN_EXPIRY }
  );
}

function generateRefreshToken(user) {
  return jwt.sign(
    { userId: user.id },
    process.env.REFRESH_TOKEN_SECRET,
    { expiresIn: process.env.REFRESH_TOKEN_EXPIRY }
  );
}

Endpoint de Login

const bcrypt = require('bcrypt');

app.post('/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // Encontrar usuário no banco de dados
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ error: 'Credenciais inválidas' });
    }
    
    // Verificar senha
    const validPassword = await bcrypt.compare(password, user.password);
    if (!validPassword) {
      return res.status(401).json({ error: 'Credenciais inválidas' });
    }
    
    // Gerar tokens
    const accessToken = generateAccessToken(user);
    const refreshToken = generateRefreshToken(user);
    
    // Armazenar token de atualização no banco de dados
    await RefreshToken.create({
      token: refreshToken,
      userId: user.id,
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
    });
    
    res.json({
      accessToken,
      refreshToken,
      user: {
        id: user.id,
        email: user.email,
        name: user.name
      }
    });
  } catch (error) {
    res.status(500).json({ error: 'Erro no servidor' });
  }
});

Middleware de Autenticação

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Token de acesso necessário' });
  }
  
  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Token inválido ou expirado' });
    }
    
    req.user = user;
    next();
  });
}

// Uso
app.get('/api/protected', authenticateToken, (req, res) => {
  res.json({ 
    message: 'Dados protegidos',
    user: req.user 
  });
});

Endpoint de Atualização de Token

app.post('/auth/refresh', async (req, res) => {
  const { refreshToken } = req.body;
  
  if (!refreshToken) {
    return res.status(401).json({ error: 'Token de atualização necessário' });
  }
  
  try {
    // Verificar se o token de atualização existe no banco de dados
    const storedToken = await RefreshToken.findOne({ token: refreshToken });
    if (!storedToken) {
      return res.status(403).json({ error: 'Token de atualização inválido' });
    }
    
    // Verificar assinatura do token
    jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, async (err, user) => {
      if (err) {
        return res.status(403).json({ error: 'Token de atualização inválido' });
      }
      
      // Obter dados do usuário
      const userData = await User.findById(user.userId);
      
      // Gerar novo token de acesso
      const accessToken = generateAccessToken(userData);
      
      res.json({ accessToken });