Seu carrinho está vazio no momento!

Introdução (3 min)
Olá, futuros arquitetos de sistemas! Bem-vindos à Aula 65, onde desvendaremos um pilar de segurança e usabilidade nas APIs modernas: os Refresh Tokens. Preparem-se para um mergulho profundo e extremamente prático.
Para começarmos, imaginem a seguinte situação. Vocês estão em um parque temático que exige um ingresso para cada atração. Adquirir um novo ingresso para cada brinquedo seria exaustivo e demorado, não é mesmo? Da mesma forma, em uma API, se a cada pequena operação vocês tivessem que refazer todo o processo de autenticação (usuário e senha), a experiência seria péssima e o sistema ficaria vulnerável a ataques de força bruta.
Agora, pensem que vocês têm um ingresso “principal” para entrar no parque (o Access Token), que dura pouco tempo para evitar que, se for roubado, o ladrão se divirta por muito tempo. Para não terem que sair do parque e comprar um novo ingresso principal do lado de fora a cada hora, vocês têm uma pulseira especial (o Refresh Token) que permite ir a um posto de atendimento interno e pegar um novo ingresso principal, sem ter que passar por toda a fila da entrada novamente. Isso é conveniente e, acima de tudo, seguro.
Nesta aula, é exatamente isso que vocês irão construir e compreender: como permitir que usuários permaneçam autenticados por períodos estendidos sem comprometer a segurança da aplicação, tudo isso utilizando Refresh Tokens. Veremos como essa funcionalidade se encaixa de forma harmoniosa no ecossistema Node.js e Express, garantindo que suas APIs sejam robustas e eficientes.
Conceito Fundamental (7 min)
Em um cenário de autenticação baseado em tokens, especialmente com JSON Web Tokens (JWT), é comum utilizarmos dois tipos distintos de tokens para gerenciar o acesso do usuário de forma eficaz e segura: o Access Token e o Refresh Token.
Access Token (Token de Acesso)
- É o “ingresso principal” que permite o acesso a recursos protegidos na API.
- Possui uma curta duração (minutos, horas), sendo renovado frequentemente.
- É enviado em cada requisição para recursos protegidos, geralmente no cabeçalho
Authorizationcomo um tokenBearer
.
- Se for interceptado por um atacante, o tempo limitado de vida minimiza o dano potencial, pois ele logo se tornará inválido.
Refresh Token (Token de Renovação)
- É a “pulseira especial” que possibilita a obtenção de um novo Access Token quando o atual expira.
- Possui uma longa duração (dias, semanas ou até meses).
- É enviado ao servidor apenas para a rota de renovação (
/refresh), e nunca para acessar recursos protegidos diretamente. - Deve ser armazenado de forma extremamente segura no lado do cliente, preferencialmente em um cookie
httpOnly, que impede o acesso via JavaScript e mitiga ataques XSS (Cross-Site Scripting). - É passível de revogação. Se um Refresh Token for comprometido ou o usuário desejar sair de todos os dispositivos, o servidor pode invalidá-lo imediatamente, bloqueando qualquer tentativa futura de renovação.
O fluxo padrão ocorre da seguinte maneira:
- O usuário efetua o login (fornecendo usuário e senha).
- O servidor, após autenticar as credenciais, gera um Access Token (de curta duração) e um Refresh Token (de longa duração).
- Ambos os tokens são enviados ao cliente. O Access Token é utilizado para as requisições normais e o Refresh Token é armazenado de forma segura.
- Quando o Access Token expira, o cliente envia o Refresh Token para um endpoint específico do servidor (e.g.,
/api/refresh-token). - O servidor valida o Refresh Token. Se for válido e não estiver revogado, um novo Access Token (e, opcionalmente, um novo Refresh Token para rotação) é gerado e enviado de volta ao cliente.
- O cliente continua a interagir com a API usando o novo Access Token, sem a necessidade de efetuar login novamente.
Casos de Uso Reais
Essa metodologia é amplamente empregada em diversas aplicações: aplicativos móveis, Single Page Applications (SPAs) como React, Angular e Vue, e aplicações desktop. Ela viabiliza uma experiência de usuário contínua, onde o usuário permanece “logado” por muito tempo, sem comprometer a integridade do sistema.
Vantagens e Desvantagens
Vantagens
- Segurança Aprimorada: Tokens de acesso de curta duração limitam a janela de oportunidade para ataques, enquanto os refresh tokens, mais seguros, permitem a renovação sem login repetido.
- Melhor Experiência do Usuário (UX): Reduz a necessidade de autenticações frequentes, contribuindo para uma navegação mais fluida e agradável.
- Revogação Flexível: Refresh tokens podem ser revogados individualmente (e.g., quando o usuário faz logout de um dispositivo específico), oferecendo controle granular sobre as sessões.
Desvantagens
- Complexidade Adicional: A arquitetura exige a gestão de dois tipos de tokens e um mecanismo de armazenamento persistente para os refresh tokens (normalmente em um banco de dados).
- Vetor de Ataque Potencial: Se um refresh token for comprometido, um atacante pode gerar novos access tokens. A revogação e rotação de refresh tokens são estratégias essenciais para mitigar este risco.
Em Node.js e Express, a integração é feita com bibliotecas como jsonwebtoken para a criação e verificação dos JWTs, e middlewares para proteger as rotas. Para o armazenamento persistente dos refresh tokens, usualmente empregamos um banco de dados, como PostgreSQL, MongoDB ou Redis.
Implementação Prática (10 min)
Vamos construir um servidor Express simples que ilustra o fluxo de autenticação com Access Tokens e Refresh Tokens. Para simplificar, armazenaremos os refresh tokens em memória, mas em um ambiente de produção, um banco de dados é essencial.
Setup Inicial
Crie um novo projeto Node.js e instale as dependências:
mkdir refresh-token-api
cd refresh-token-api
npm init -y
npm install express jsonwebtoken dotenv cookie-parser
Crie um arquivo .env na raiz do projeto para suas chaves secretas:
ACCESS_TOKEN_SECRET='sua_chave_secreta_para_access_tokens'
REFRESH_TOKEN_SECRET='sua_chave_secreta_para_refresh_tokens_mais_longa'
PORT=3000
Agora, crie o arquivo principal da sua API, app.js:
// app.js
// 1. Carrega variáveis de ambiente do arquivo .env
require('dotenv').config();
// 2. Importa os módulos essenciais
const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser'); // Para lidar com cookies de forma eficiente
// 3. Inicializa a aplicação Express
const app = express();
const port = process.env.PORT || 3000; // Define a porta, ou usa 3000 como padrão
// 4. Configurações e Middlewares
app.use(express.json()); // Habilita o Express a parsear JSON no corpo das requisições
app.use(cookieParser()); // Habilita o uso de cookies
// Simulação de "banco de dados" para refresh tokens (em produção, use um DB real!)
// ATENÇÃO: Em ambiente real, esta lista seria persistida em um banco de dados.
let refreshTokens = [];
// Chaves secretas para assinar e verificar os tokens JWT
const accessTokenSecret = process.env.ACCESS_TOKEN_SECRET;
const refreshTokenSecret = process.env.REFRESH_TOKEN_SECRET;
// 5. Middleware de autenticação para proteger rotas
function authenticateToken(req, res, next) {
// 5.1 Obtém o token de acesso do cabeçalho Authorization
const authHeader = req.headers['authorization'];
// Formato esperado: "Bearer SEU_ACCESS_TOKEN"
const token = authHeader && authHeader.split(' ')[1];
// 5.2 Se não houver token, retorna 401 (Não Autorizado)
if (token == null) {
console.log('Tentativa de acesso sem token.');
return res.sendStatus(401);
}
// 5.3 Verifica o token de acesso
jwt.verify(token, accessTokenSecret, (err, user) => {
// Se houver erro (token inválido ou expirado), retorna 403 (Proibido)
if (err) {
console.log('Token de acesso inválido ou expirado:', err.message);
return res.sendStatus(403);
}
// Se o token for válido, armazena as informações do usuário na requisição
req.user = user;
// 5.4 Continua para a próxima função middleware ou rota
next();
});
}
// 6. Rotas da API
// 6.1 Rota de Login: Gera Access Token e Refresh Token
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
// TODO: Em uma aplicação real, você validaria o username e password contra um DB
// Por simplicidade, simulamos um usuário válido.
if (!username || !password || username !== 'user' || password !== 'pass') {
console.log(Tentativa de login falha para ${username}.);
return res.status(400).json({ message: 'Credenciais inválidas.' });
}
// Objeto do usuário (payload do JWT)
const user = { name: username };
// 6.1.1 Gera o Access Token (curta duração)
const accessToken = jwt.sign(user, accessTokenSecret, { expiresIn: '15m' }); // Expira em 15 minutos
// 6.1.2 Gera o Refresh Token (longa duração)
const refreshToken = jwt.sign(user, refreshTokenSecret, { expiresIn: '7d' }); // Expira em 7 dias
// 6.1.3 Armazena o Refresh Token (simulação de DB)
refreshTokens.push(refreshToken);
console.log(Usuário ${username} logado. Access Token gerado. Refresh Token armazenado.);
// 6.1.4 Envia o Refresh Token como um cookie httpOnly seguro
// O Refresh Token não deve ser acessível via JavaScript para maior segurança.
res.cookie('refreshToken', refreshToken, {
httpOnly: true, // Impede acesso via JavaScript
secure: process.env.NODE_ENV === 'production', // Apenas via HTTPS em produção
sameSite: 'strict', // Proteção contra CSRF
maxAge: 7 24 60 60 1000 // 7 dias em milissegundos
});
// 6.1.5 Retorna o Access Token (e possivelmente o refresh token no corpo, mas cookies são melhores)
res.json({ accessToken: accessToken, message: 'Login bem-sucedido.' });
});
// 6.2 Rota de Renovação de Token: Usa o Refresh Token para obter um novo Access Token
app.post('/api/refresh-token', (req, res) => {
// 6.2.1 Obtém o Refresh Token do cookie
const refreshToken = req.cookies.refreshToken;
// 6.2.2 Se não houver Refresh Token, retorna 401
if (!refreshToken) {
console.log('Tentativa de renovação sem Refresh Token.');
return res.status(401).json({ message: 'Refresh Token não fornecido.' });
}
// 6.2.3 Verifica se o Refresh Token está na nossa "lista" (simulação de DB)
if (!refreshTokens.includes(refreshToken)) {
console.log('Refresh Token inválido ou não encontrado na lista.');
return res.status(403).json({ message: 'Refresh Token inválido.' });
}
// 6.2.4 Verifica a validade do Refresh Token
jwt.verify(refreshToken, refreshTokenSecret, (err, user) => {
if (err) {
console.log('Refresh Token expirado ou inválido:', err.message);
// Se o Refresh Token for inválido, remova-o da lista (se existir)
refreshTokens = refreshTokens.filter(token => token !== refreshToken);
// Limpa o cookie do cliente
res.clearCookie('refreshToken');
return res.status(403).json({ message: 'Refresh Token expirado ou inválido. Faça login novamente.' });
}
// 6.2.5 Se válido, gera um novo Access Token
const newAccessToken = jwt.sign({ name: user.name }, accessTokenSecret, { expiresIn: '15m' });
console.log(Novo Access Token gerado para ${user.name}.);
// OPCIONAL: Rotação de Refresh Tokens (gera um novo Refresh Token e invalida o antigo)
// const newRefreshToken = jwt.sign({ name: user.name }, refreshTokenSecret, { expiresIn: '7d' });
// refreshTokens = refreshTokens.filter(token => token !== refreshToken); // Remove o antigo
// refreshTokens.push(newRefreshToken); // Adiciona o novo
// res.cookie('refreshToken', newRefreshToken, {
// httpOnly: true,
// secure: process.env.NODE_ENV === 'production',
// sameSite: 'strict',
// maxAge: 7 24 60 60 1000
// });
res.json({ accessToken: newAccessToken, message: 'Token de acesso renovado com sucesso.' });
});
});
// 6.3 Rota de Logout: Revoga o Refresh Token
app.post('/api/logout', (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) {
console.log('Tentativa de logout sem Refresh Token no cookie.');
return res.status(204).json({ message: 'Nenhum Refresh Token para remover.' }); // 204 No Content
}
// Remove o Refresh Token da nossa "lista" (revoga)
refreshTokens = refreshTokens.filter(token => token !== refreshToken);
console.log('Refresh Token removido da lista (revogado).');
// Limpa o cookie do navegador
res.clearCookie('refreshToken', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
});
console.log('Cookie de Refresh Token limpo.');
res.status(204).json({ message: 'Logout bem-sucedido.' });
});
// 6.4 Rota Protegida: Apenas usuários com Access Token válido podem acessar
app.get('/api/protected-data', authenticateToken, (req, res) => {
// req.user contém as informações do usuário do Access Token
console.log(Acesso à rota protegida por ${req.user.name}.);
res.json({ message: Bem-vindo, ${req.user.name}! Você acessou dados protegidos., data: 'Informações confidenciais aqui.' });
});
// 7. Inicializa o servidor
app.listen(port, () => {
console.log(Servidor rodando em http://localhost:${port});
console.log('Para testar:');
console.log('1. Fazer login: curl -X POST -H "Content-Type: application/json" -d \'{"username":"user","password":"pass"}\' http://localhost:3000/api/login');
console.log('2. Acessar rota protegida (com o token do passo 1): curl -H "Authorization: Bearer SEU_ACCESS_TOKEN" http://localhost:3000/api/protected-data');
console.log('3. Renovar token: curl -X POST http://localhost:3000/api/refresh-token --cookie "refreshToken=SEU_REFRESH_TOKEN_DO_COOKIE"');
console.log('4. Fazer logout: curl -X POST http://localhost:3000/api/logout --cookie "refreshToken=SEU_REFRESH_TOKEN_DO_COOKIE"');
});
Configurações para HostGator Plano M
Para implantar em um ambiente como o HostGator Plano M, é vital considerar alguns pontos:
- Variáveis de Ambiente (
.env): O HostGator geralmente permite configurar variáveis de ambiente diretamente no cPanel ou via um arquivo de configuração de inicialização. Evite deixar chaves secretas diretamente no código-fonte. - Porta (
process.env.PORT): Em hosts compartilhados, sua aplicação Node.js pode não rodar diretamente na porta 80/443. Um servidor proxy (como Nginx ou Apache no HostGator) geralmente encaminha requisições da porta padrão para a porta interna da sua aplicação Node. Certifique-se de que sua aplicação escute na porta fornecida pelo ambiente, ou em uma porta padrão como 3000, e que o proxy esteja configurado corretamente para encaminhar. - Armazenamento Persistente: Em produção, o array
refreshTokensem memória não funcionará, pois será resetado a cada reinício do servidor. Use um banco de dados real (MySQL/PostgreSQL, ou MongoDB se o plano suportar) para armazenar os refresh tokens. - HTTPS (
secure: true): Configuresecure: truepara seus cookies em produção. No HostGator, isso significa que você precisará ter um certificado SSL/TLS ativo. - Logging: Em vez de apenas
console.log, considere bibliotecas de logging mais robustas comoWinstonouPinopara gerenciar logs em arquivos, facilitando a depuração em um ambiente de servidor.
Error Handling Eficiente
No código, utilizamos blocos if e return res.status(XYZ).json(...) para lidar com erros como tokens inválidos ou ausentes. Isso assegura que a API retorne respostas claras e códigos de status HTTP apropriados, um pilar das melhores práticas.
Testes Básicos (usando curl)
Você pode testar o fluxo com os comandos curl que já estão documentados no final do arquivo app.js:
# 1. Login (obtém Access Token no corpo e Refresh Token no cookie)
curl -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"pass"}' http://localhost:3000/api/login -c cookie-jar.txt -D headers.txt
Do arquivo 'headers.txt', você vai precisar do Access Token (Bearer)
📚 Informações da Aula
Curso: API Completo - Node.js & Express
Tempo estimado: 25 minutos
Pré-requisitos: JavaScript básico
Do cookie-jar.txt, você vai precisar do Refresh Token (ele será enviado automaticamente nos próximos curls com -b cookie-jar.txt)
Exemplo de Access Token obtido: "eyJhbGciOiJIUzI1Ni..."
2. Acessar Rota Protegida (substitua SEU_ACCESS_TOKEN)
curl -H "Authorization: Bearer SEU_ACCESS_TOKEN" http://localhost:3000/api/protected-data -b cookie-jar.txt
3. Renovar Token (o refresh token será enviado automaticamente do cookie-jar)
curl -X POST http://localhost:3000/api/refresh-token -b cookie-jar.txt -c cookie-jar.txt
O comando acima retornará um NOVO Access Token. Você pode usá-lo para acessar a rota protegida novamente.
4. Fazer Logout (o refresh token será enviado automaticamente do cookie-jar e revogado)
curl -X POST http://localhost:3000/api/logout -b cookie-jar.txt -c cookie-jar.txt
Para os comandos curl, a flag -c cookie-jar.txt salva os cookies recebidos em um arquivo, e -b cookie-jar.txt os envia em requisições futuras, simulando o comportamento de um navegador. Use -D headers.txt para ver os cabeçalhos da resposta, onde o Set-Cookie para o refresh token estará visível.
Exercício Hands-On (5 min)
Para solidificar seu aprendizado, proponho um desafio prático:
Desafio
Modifique a implementação atual para que os refresh tokens não sejam armazenados em um array em memória, mas sim em um arquivo JSON. Isso simulará um armazenamento persistente e permitirá que os refresh tokens sobrevivam a reinícios do servidor, como aconteceria com um banco de dados real. Além disso, certifique-se de que a rota /api/logout remova o refresh token do arquivo JSON.
Solução Detalhada Passo a Passo
- Crie o arquivo de armazenamento:
Crie um arquivo chamado
refreshTokens.jsonna raiz do seu projeto. Inicialmente, ele pode conter um array vazio:// refreshTokens.json [] - Adicione funções para ler e escrever no arquivo JSON:
No
app.js, adicione as seguintes funções. Elas serão responsáveis por interagir com seu “banco de dados” de refresh tokens.// ... no início do app.js, após os imports ... const fs = require('fs'); // Módulo nativo do Node.js para manipulação de arquivosconst REFRESH_TOKENS_FILE = 'refreshTokens.json';
// Função para ler os refresh tokens do arquivo function readRefreshTokens() { try { const data = fs.readFileSync(REFRESH_TOKENS_FILE, 'utf8'); return JSON.parse(data); } catch (error) { // Se o arquivo não existir ou for inválido, retorna um array vazio console.error('Erro ao ler refreshTokens.json, criando um novo arquivo ou array vazio.', error.message); return []; } }
// Função para escrever os refresh tokens no arquivo function writeRefreshTokens(tokens) { try { fs.writeFileSync(REFRESH_TOKENS_FILE, JSON.stringify(tokens, null, 2), 'utf8'); } catch (error) { console.error('Erro ao escrever em refreshTokens.json.', error.message); } }
// Inicializa o array de refresh tokens lendo do arquivo let refreshTokens = readRefreshTokens();
// ... o restante do seu código ...
- Modifique as rotas
loginerefresh-tokenpara usar as novas funções:- Na rota
/api/login, ao gerar um novo refresh token:// Antigo: refreshTokens.push(refreshToken); // Novo: refreshTokens.push(refreshToken); writeRefreshTokens(refreshTokens); // Salva no arquivo console.log(Usuário ${username} logado. Access Token gerado. Refresh Token armazenado e persistido.); - Na rota
/api/refresh-token, ao revogar um token inválido e ao gerenciar a rotação (se implementado):// Quando o Refresh Token for inválido/expirado: // Antigo: refreshTokens = refreshTokens.filter(token => token !== refreshToken); // Novo: refreshTokens = refreshTokens.filter(token => token !== refreshToken); writeRefreshTokens(refreshTokens); // Salva a lista atualizada // ...// Se você implementar a rotação de refresh tokens: // refreshTokens = refreshTokens.filter(token => token !== refreshToken); // refreshTokens.push(newRefreshToken); // writeRefreshTokens(refreshTokens); // Salva a lista atualizada com o novo token
- Na rota
- Modifique a rota
logoutpara remover o token do arquivo:// Na rota /api/logout: // Antigo: refreshTokens = refreshTokens.filter(token => token !== refreshToken); // Novo: refreshTokens = refreshTokens.filter(token => token !== refreshToken); writeRefreshTokens(refreshTokens); // Salva a lista atualizada sem o token revogado console.log('Refresh Token removido da lista (revogado) e persistido.'); // ...
Como Testar e Validar o Resultado
Execute o servidor (node app.js). Utilize os comandos curl da seção de implementação prática para testar o login, acesso protegido, renovação e logout. Após cada operação que modifica os refresh tokens (login, logout, refresh de um token inválido), verifique o conteúdo do arquivo refreshTokens.json para garantir que ele esteja sendo atualizado corretamente.
Um teste crucial será: faça login, reinicie o servidor Node.js, e tente renovar o token. Se o Refresh Token ainda for válido e estiver no arquivo JSON, ele deverá funcionar, provando que o armazenamento está persistente.
Troubleshooting dos Erros Mais Comuns
ENOENT: no such file or directory, open 'refreshTokens.json'(Erro ao ler/escrever): Significa que o arquivorefreshTokens.jsonnão existe ou o caminho está incorreto. Crie o arquivo na raiz do projeto.SyntaxError: Unexpected token o in JSON at position 1(Erro no JSON.parse): Indica que o arquivorefreshTokens.jsonnão contém JSON válido. Verifique se ele está no formato correto (e.g.,[]).- Token inválido/expirado após reiniciar o servidor: Se isso acontecer após implementar o armazenamento em arquivo, verifique se as funções
readRefreshTokensewriteRefreshTokensestão sendo chamadas nos locais corretos e se estão realmente salvando e carregando os dados.
Próximos Passos Sugeridos
Para levar sua API ao próximo patamar:
- Integração com um Banco de Dados Real: Substitua o arquivo JSON por um banco de dados como PostgreSQL (com
pg), MongoDB (commongoose) ou Redis (comioredis) para gerenciar os refresh tokens de forma verdadeiramente escalável e robusta. - Rotação de Refresh Tokens: Implemente a lógica de rotação, onde a cada renovação, um novo Refresh Token é gerado e o antigo é invalidado. Isso aumenta a segurança ao limitar ainda mais a utilidade de um token comprometido.
- Blacklisting de Access Tokens: Para revogação imediata de Access Tokens (e.g., em um logout), use uma “blacklist” (geralmente em memória ou em Redis) para marcar Access Tokens como inválidos antes de sua expiração natural.
- Auditoria e Detecção de Reuso: Implemente mecanismos para detectar tentativas de reuso de Refresh Tokens que já deveriam ter sido invalidados, indicando uma possível tentativa de ataque.
🚀 Pronto para a próxima aula?
Continue sua jornada no desenvolvimento de APIs e domine Node.js & Express!