API Gateway como Camada de Segurança: Autenticação, WAF e Logging
Um API Gateway é um servidor que funciona como intermediário entre clientes e serviços backend. Sua importância reside em centralizar operações críticas como autenticação, validação de requisições e auditoria. Sem essa camada, cada serviço precisaria implementar essas funcionalidades individualmente, criando redundância, inconsistência e pontos cegos de segurança. Neste artigo, você aprenderá a implementar um API Gateway robusto com foco em três pilares: autenticação segura, proteção contra ataques web (WAF) e logging detalhado para auditoria.
O papel do API Gateway vai além de rotear requisições. Ele é a porta de entrada do seu sistema, responsável por validar identidades, bloquear padrões suspeitos e manter um registro imutável de tudo que acontece. Compreender essa responsabilidade é fundamental para construir arquiteturas seguras e escaláveis.
Autenticação no API Gateway
Conceitos Fundamentais
Autenticação é o processo de verificar a identidade de quem está fazendo a requisição. Diferentes tipos de autenticação atendem a diferentes necessidades: autenticação básica para sistemas simples, tokens JWT para APIs modernas e OAuth2 para delegação de acesso. O API Gateway não apenas autentica, mas também valida a autorização—quais recursos o usuário autenticado pode acessar.
A decisão sobre onde autenticar é estratégica. Centralizar a autenticação no API Gateway oferece uma visão única de quem acessa o sistema e facilita mudanças futuras sem modificar código em todos os serviços. Além disso, você pode rejeitar requisições inválidas antes delas chegarem aos serviços backend, economizando recursos.
Implementação com JWT
JWT (JSON Web Token) é o padrão mais comum em APIs modernas. Um JWT contém três partes separadas por pontos: header, payload e signature. O servidor valida a assinatura para confirmar que o token não foi alterado. Vamos implementar um API Gateway em Node.js com Express que autentica requisições usando JWT:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const SECRET_KEY = 'sua_chave_secreta_muito_segura_aqui';
// Middleware de autenticação
function autenticar(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
return res.status(401).json({ erro: 'Token não fornecido' });
}
try {
const decoded = jwt.verify(token, SECRET_KEY);
req.usuario = decoded;
next();
} catch (erro) {
if (erro.name === 'TokenExpiredError') {
return res.status(401).json({ erro: 'Token expirado' });
}
return res.status(403).json({ erro: 'Token inválido' });
}
}
// Endpoint para gerar token (login)
app.post('/auth/login', (req, res) => {
// Em produção, você verificaria credenciais contra um banco de dados
const usuario = {
id: 1,
email: 'usuario@exemplo.com',
role: 'user'
};
const token = jwt.sign(usuario, SECRET_KEY, { expiresIn: '1h' });
res.json({ token, expiresIn: '1h' });
});
// Endpoint protegido
app.get('/api/perfil', autenticar, (req, res) => {
res.json({
mensagem: 'Acesso permitido',
usuario: req.usuario
});
});
// Endpoint protegido com verificação de role
app.delete('/api/usuarios/:id', autenticar, (req, res) => {
if (req.usuario.role !== 'admin') {
return res.status(403).json({ erro: 'Acesso negado. Apenas administradores podem deletar usuários.' });
}
// Lógica de deleção aqui
res.json({ mensagem: 'Usuário deletado com sucesso' });
});
app.listen(3000, () => {
console.log('API Gateway rodando na porta 3000');
});
Esse código demonstra como validar tokens no gateway antes de rotear para os serviços reais. Note que o payload do JWT inclui informações sobre o usuário (como role), permitindo decisões de autorização no próprio gateway. Tokens expirados são rejeitados automaticamente, forçando o cliente a fazer login novamente.
Refresh Tokens para Maior Segurança
Em aplicações reais, você não quer que tokens de acesso durem muito tempo (mais de 1 hora é considerado inseguro). Para melhor experiência do usuário, implementamos refresh tokens que são válidos por períodos mais longos. O cliente usa o refresh token para obter um novo access token sem fazer login novamente:
const tokens = {}; // Em produção, use um banco de dados ou Redis
app.post('/auth/login', (req, res) => {
const usuario = {
id: 1,
email: 'usuario@exemplo.com',
role: 'user'
};
const accessToken = jwt.sign(usuario, SECRET_KEY, { expiresIn: '15m' });
const refreshToken = jwt.sign({ id: usuario.id }, SECRET_KEY, { expiresIn: '7d' });
// Armazenar refresh token associado ao usuário
tokens[refreshToken] = usuario.id;
res.json({
accessToken,
refreshToken,
expiresIn: '15m'
});
});
app.post('/auth/refresh', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken || !tokens[refreshToken]) {
return res.status(401).json({ erro: 'Refresh token inválido' });
}
try {
const decoded = jwt.verify(refreshToken, SECRET_KEY);
const usuario = { id: decoded.id, email: 'usuario@exemplo.com', role: 'user' };
const novoAccessToken = jwt.sign(usuario, SECRET_KEY, { expiresIn: '15m' });
res.json({ accessToken: novoAccessToken, expiresIn: '15m' });
} catch (erro) {
return res.status(403).json({ erro: 'Refresh token expirado' });
}
});
Esse padrão oferece segurança sem sacrificar usabilidade. Access tokens curtos minimizam danos se comprometidos, e refresh tokens longos evitam que o usuário faça login constantemente.
Web Application Firewall (WAF) no Gateway
Fundamentos e Estratégias de Proteção
Um WAF é um conjunto de regras que inspeciona requisições HTTP/HTTPS para detectar e bloquear ataques comuns. Diferentemente de um firewall tradicional que trabalha com IPs e portas, um WAF entende a semântica das requisições HTTP: SQL injection, cross-site scripting (XSS), path traversal e outros ataques específicos da camada de aplicação. No API Gateway, implementar um WAF significa adicionar camadas de validação antes do tráfego chegar aos serviços reais.
As principais categorias de ataques que um WAF deve mitigar incluem injeção de código, validação inadequada de entrada e comportamentos anormais. Uma estratégia eficaz combina validação de schema (esperar que dados sigam um formato específico), sanitização de entrada (remover caracteres perigosos) e rate limiting (limitar requisições por IP ou usuário).
Implementação Prática de WAF
Vamos construir um middleware WAF completo que inclui validação de entrada, proteção contra SQL injection, XSS e rate limiting:
const express = require('express');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const helmet = require('helmet');
const app = express();
// Helmet adiciona headers de segurança HTTP
app.use(helmet());
// Sanitização contra NoSQL injection
app.use(mongoSanitize());
// Parse JSON com limite de tamanho para evitar ataques de negação de serviço
app.use(express.json({ limit: '10kb' }));
// Rate limiting: máximo 100 requisições por janela de 15 minutos
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Muitas requisições deste IP, tente novamente mais tarde.',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
// WAF personalizado para detectar padrões suspeitos
const waf = (req, res, next) => {
// Padrões que indicam SQL injection
const sqlInjectionPatterns = [
/(\b(UNION|SELECT|INSERT|UPDATE|DELETE|DROP|EXEC|EXECUTE)\b)/gi,
/(-{2}|\/\*|\*\/|xp_|sp_)/gi,
/(;|\||&&)/g
];
// Padrões de XSS
const xssPatterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
/javascript:/gi,
/on\w+\s*=/gi,
/<iframe/gi
];
// Verificar corpo da requisição
const body = JSON.stringify(req.body);
const url = req.url;
const input = body + url;
for (const pattern of sqlInjectionPatterns) {
if (pattern.test(input)) {
console.warn(`[WAF] SQL Injection detectada em ${req.method} ${req.path}`);
return res.status(403).json({ erro: 'Requisição bloqueada por WAF' });
}
}
for (const pattern of xssPatterns) {
if (pattern.test(input)) {
console.warn(`[WAF] XSS detectada em ${req.method} ${req.path}`);
return res.status(403).json({ erro: 'Requisição bloqueada por WAF' });
}
}
// Validação de Content-Type
if (req.method !== 'GET' && req.method !== 'HEAD') {
const contentType = req.headers['content-type'];
if (!contentType || !contentType.includes('application/json')) {
return res.status(400).json({ erro: 'Content-Type deve ser application/json' });
}
}
next();
};
app.use(waf);
// Exemplo de endpoint protegido por WAF
app.post('/api/dados', (req, res) => {
const { nome, email } = req.body;
// Validação adicional no nível da aplicação
if (!nome || typeof nome !== 'string' || nome.length > 100) {
return res.status(400).json({ erro: 'Nome inválido' });
}
if (!email || !email.includes('@')) {
return res.status(400).json({ erro: 'Email inválido' });
}
res.json({ mensagem: 'Dados recebidos com segurança', nome, email });
});
app.listen(3000, () => {
console.log('API Gateway com WAF rodando na porta 3000');
});
Este exemplo demonstra como implementar um WAF em camadas. O Helmet adiciona headers de segurança, express-mongo-sanitize remove caracteres maliciosos, rate limiting previne força bruta, e o middleware customizado detecta padrões de ataque conhecidos. Cada camada adiciona redundância—um ataque bloqueado em uma camada não afeta o resto do sistema.
Proteção contra Path Traversal e DoS
Path traversal (tentativa de acessar arquivos fora do diretório permitido) e ataques de negação de serviço (DoS) são ameaças comuns. Vamos adicionar proteção específica:
// Proteção contra path traversal
const protegerPathTraversal = (req, res, next) => {
const caminhosBloqueados = [
/\.\.[\\/]/, // ../
/\.\.%2f/gi, // ..%2f (URL encoded)
/\.\.%5c/gi, // ..%5c (URL encoded)
];
const caminho = decodeURIComponent(req.url);
for (const pattern of caminhosBloqueados) {
if (pattern.test(caminho)) {
console.warn(`[WAF] Path traversal detectada: ${caminho}`);
return res.status(403).json({ erro: 'Acesso negado' });
}
}
next();
};
app.use(protegerPathTraversal);
// Rate limiting mais agressivo para endpoints sensíveis
const limiterSensivel = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 5,
skipSuccessfulRequests: false, // Contar mesmo requisições bem-sucedidas
});
app.post('/auth/login', limiterSensivel, (req, res) => {
// Lógica de login aqui
res.json({ token: 'xyz' });
});
Essas proteções impedirão que atacantes acessem arquivos do sistema e façam força bruta em endpoints sensíveis. A chave é ser agressivo com rate limiting em operações de segurança.
Logging e Auditoria
Estratégia de Logging em Camadas
Logging efetivo é o alicerce da segurança. Um bom sistema de logging registra o quê, quem, quando e onde algo aconteceu. No API Gateway, você deve registrar: autenticações bem-sucedidas e falhadas, requisições bloqueadas por WAF, erros e qualquer coisa que saia do padrão esperado. Logs devem ser imutáveis (impossíveis de alterar retroativamente), centralizados (em um único lugar) e rapidamente pesquisáveis.
A estratégia de logging deve ser granular mas não excessiva. Registrar cada byte de tráfego gera ruído que dificulta detectar anomalias reais. Em vez disso, você deve registrar eventos significativos e agrupar logs em categorias (autenticação, autorização, ataques bloqueados) para análise eficiente.
Implementação com Winston e ELK Stack
Winston é a biblioteca padrão para logging em Node.js. Vamos implementar um sistema que envia logs para um arquivo local e para um serviço centralizado (simulado aqui):
const express = require('express');
const winston = require('winston');
const app = express();
// Configurar Winston com múltiplos transportadores
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { servico: 'api-gateway' },
transports: [
// Arquivo para erros críticos
new winston.transports.File({
filename: 'logs/erros.log',
level: 'error',
maxsize: 10485760, // 10MB
maxFiles: 5,
}),
// Arquivo para todos os logs
new winston.transports.File({
filename: 'logs/combinado.log',
maxsize: 10485760,
maxFiles: 10,
}),
// Console em desenvolvimento
new winston.transports.Console({
format: winston.format.simple()
})
]
});
// Middleware para registrar todas as requisições
app.use((req, res, next) => {
const inicio = Date.now();
// Interceptar resposta para registrar status
res.on('finish', () => {
const duracao = Date.now() - inicio;
logger.info('Requisição processada', {
metodo: req.method,
caminho: req.path,
status: res.statusCode,
duracao: `${duracao}ms`,
ip: req.ip,
userAgent: req.headers['user-agent'],
usuario: req.usuario?.email || 'anonimo',
});
});
next();
});
// Registrar tentativas de autenticação
app.post('/auth/login', (req, res) => {
const { email } = req.body;
// Simular validação
const autenticacaoValida = email === 'usuario@exemplo.com';
if (autenticacaoValida) {
const token = 'token_simulado';
logger.info('Autenticação bem-sucedida', {
usuario: email,
ip: req.ip,
timestamp: new Date().toISOString(),
});
res.json({ token });
} else {
logger.warn('Tentativa de autenticação falhada', {
usuario: email,
ip: req.ip,
motivo: 'Credenciais inválidas',
timestamp: new Date().toISOString(),
});
res.status(401).json({ erro: 'Credenciais inválidas' });
}
});
// Registrar eventos de segurança (WAF, etc)
app.use((req, res, next) => {
// Se WAF bloqueou, registrar como crítico
if (res.statusCode === 403) {
logger.warn('Requisição bloqueada por WAF', {
metodo: req.method,
caminho: req.path,
ip: req.ip,
corpo: req.body,
razao: 'Padrão malicioso detectado',
});
}
next();
});
// Tratador de erros centralizado
app.use((err, req, res, next) => {
logger.error('Erro não tratado', {
mensagem: err.message,
stack: err.stack,
caminho: req.path,
metodo: req.method,
usuario: req.usuario?.email || 'anonimo',
ip: req.ip,
});
res.status(500).json({ erro: 'Erro interno do servidor' });
});
app.listen(3000, () => {
logger.info('API Gateway iniciado na porta 3000');
});
Essa implementação registra toda requisição, falhas de autenticação, bloqueios de WAF e erros não tratados. Cada log inclui contexto (IP, usuário, horário) que permite rastrear eventos e investigar incidentes de segurança.
Análise e Alertas em Tempo Real
Registrar logs é apenas metade da solução. Você precisa analisá-los e gerar alertas para ameaças detectadas. Vamos implementar um analisador simples que detecta padrões suspeitos:
// Analisador de anomalias
class AnalisadorSeguranca {
constructor() {
this.tentativasLogin = {}; // { ip: [timestamps] }
this.requisicoesBloqueadas = {}; // { ip: count }
}
registrarTentativaLogin(ip, sucesso) {
if (!this.tentativasLogin[ip]) {
this.tentativasLogin[ip] = [];
}
this.tentativasLogin[ip].push({
timestamp: Date.now(),
sucesso
});
// Manter apenas últimas 10 tentativas
if (this.tentativasLogin[ip].length > 10) {
this.tentativasLogin[ip].shift();
}
// Detectar força bruta: 5 tentativas falhadas em 1 minuto
const agora = Date.now();
const umMinutoAtras = agora - 60000;
const tentativasFalhadas = this.tentativasLogin[ip].filter(
t => !t.sucesso && t.timestamp > umMinutoAtras
).length;
if (tentativasFalhadas >= 5) {
logger.error('ALERTA: Tentativa de força bruta detectada', {
ip,
tentativasFalhadas,
acao: 'Bloquear IP por 15 minutos',
});
return 'BLOQUEAR';
}
return 'PERMITIR';
}
registrarRequisicaoBloqueada(ip) {
this.requisicoesBloqueadas[ip] = (this.requisicoesBloqueadas[ip] || 0) + 1;
// Alerta se mais de 10 requisições bloqueadas do mesmo IP em 5 minutos
if (this.requisicoesBloqueadas[ip] > 10) {
logger.error('ALERTA: Padrão de ataque detectado', {
ip,
requisicoesBloqueadas: this.requisicoesBloqueadas[ip],
acao: 'Investigar IP',
});
}
}
}
const analisador = new AnalisadorSeguranca();
// Integrar analisador com login
app.post('/auth/login', (req, res) => {
const ip = req.ip;
const { email } = req.body;
// Verificar se IP está em força bruta
const statusSeguranca = analisador.registrarTentativaLogin(ip, false);
if (statusSeguranca === 'BLOQUEAR') {
return res.status(429).json({
erro: 'Muitas tentativas de login. Tente novamente em 15 minutos.'
});
}
// ... resto da lógica de login
analisador.registrarTentativaLogin(ip, true);
res.json({ token: 'xyz' });
});
Esse analisador detecta padrões anormais (força bruta, varredura de vulnerabilidades) e gera alertas para investigação manual ou ação automática como bloqueio de IP.
Conclusão
Neste artigo, você aprendeu os três pilares de um API Gateway seguro. Primeiro, autenticação centralizada com JWT e refresh tokens garante que apenas usuários legítimos acessem recursos, enquanto mantém a segurança sem sacrificar usabilidade. Segundo, WAF em múltiplas camadas (validação de schema, sanitização, rate limiting e detecção de padrões) bloqueia classes inteiras de ataques no perímetro do sistema, evitando que ataquem seus serviços backend. Terceiro, logging e análise em tempo real fornecem visibilidade total do sistema e permitem detectar e responder a incidentes rapidamente.
Esses três componentes não são isolados—eles trabalham em conjunto. Um usuário autenticado que tenta SQL injection é bloqueado pelo WAF e registrado no log com contexto completo. Esse contexto ajuda a detectar padrões de ataque e refinar as defesas futuras. Implementar essa arquitetura exige rigor, mas o retorno em segurança, conformidade (LGPD, GDPR) e confiança dos usuários justifica completamente o esforço.