Segurança de APIs REST
Esta aula aborda os fundamentos de segurança em APIs REST, cobrindo autenticação, rate limiting, validação de entrada e gerenciamento de segredos. São apresentadas boas práticas e exemplos práticos para proteger APIs contra ameaças comuns.
A segurança de APIs REST é um tópico crítico no desenvolvimento de sistemas modernos, pois APIs expõem funcionalidades e dados sensíveis a clientes externos. Uma API mal protegida pode ser explorada por atacantes para obter acesso não autorizado, sobrecarregar servidores ou injetar código malicioso. Nesta aula, exploraremos quatro pilares essenciais para proteger APIs REST: autenticação, rate limiting, validação de entrada e gerenciamento de segredos.
Cada um desses pilares aborda uma dimensão diferente da segurança: autenticação verifica quem está fazendo a requisição; rate limiting controla a frequência de requisições; validação de entrada garante que os dados recebidos são seguros; e secrets management protege credenciais e chaves usadas internamente. Ao final, você será capaz de implementar medidas de segurança robustas em suas APIs.
Autenticação
Autenticação é o processo de verificar a identidade de um cliente que faz uma requisição à API. Sem autenticação, qualquer pessoa poderia acessar recursos restritos. Os métodos mais comuns incluem API keys, autenticação HTTP Basic, tokens JWT (JSON Web Tokens) e OAuth 2.0. Cada um tem suas vantagens e desvantagens em termos de segurança e usabilidade.
API keys são simples de implementar, mas devem ser tratadas como senhas e nunca expostas em URLs ou logs. JWT é amplamente usado em aplicações modernas por ser stateless e permitir carregar informações do usuário no token. OAuth 2.0 é um padrão robusto para delegação de acesso, frequentemente usado em cenários onde terceiros precisam acessar a API em nome do usuário. Independentemente do método, é crucial usar HTTPS para proteger as credenciais em trânsito e armazenar chaves de forma segura no servidor.
// Exemplo de validação de JWT em Node.js (pseudo-código)
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Token ausente' });
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Token inválido' });
req.user = user;
next();
});
}Rate limiting
Rate limiting é uma técnica para controlar o número de requisições que um cliente pode fazer em um determinado intervalo de tempo. Isso protege a API contra abusos, como ataques de força bruta, DDoS e uso excessivo que pode degradar o serviço para outros usuários. Sem rate limiting, um atacante poderia tentar milhões de senhas em um endpoint de login ou sobrecarregar o servidor com requisições.
Existem várias estratégias de rate limiting: por IP, por usuário autenticado, por endpoint, ou uma combinação delas. Ferramentas como Redis ou bancos de dados em memória são frequentemente usadas para armazenar contadores de requisições. A resposta HTTP deve incluir cabeçalhos como X-RateLimit-Limit, X-RateLimit-Remaining e Retry-After para informar o cliente sobre os limites. Uma prática comum é usar o algoritmo de token bucket ou sliding window para implementar rate limiting de forma eficiente.
// Exemplo de rate limiting simples usando Redis (pseudo-código)
const redis = require('redis');
const client = redis.createClient();
const WINDOW_SIZE = 60; // segundos
const MAX_REQUESTS = 100;
async function rateLimit(req, res, next) {
const key = `rate:${req.ip}`;
const current = await client.incr(key);
if (current === 1) {
await client.expire(key, WINDOW_SIZE);
}
if (current > MAX_REQUESTS) {
return res.status(429).json({ error: 'Muitas requisições' });
}
next();
}Input validation
Validação de entrada é o processo de verificar se os dados recebidos pela API estão no formato esperado e não contêm conteúdo malicioso. Ataques como injeção de SQL, cross-site scripting (XSS) e command injection exploram a falta de validação para executar código não autorizado. Toda entrada deve ser considerada não confiável até que seja validada e sanitizada.
As práticas recomendadas incluem: usar listas brancas (allowlists) para tipos de dados, tamanhos e padrões; validar no servidor mesmo que o cliente já tenha validado; utilizar bibliotecas de validação robustas; e sanitizar entradas que serão exibidas em páginas web. Por exemplo, ao receber um campo de email, deve-se verificar se ele corresponde a um padrão de email válido e limitar seu comprimento. Nunca concatene entradas diretamente em consultas SQL ou comandos do sistema.
// Exemplo de validação de entrada em Python (pseudo-código)
from flask import request, jsonify
import re
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return False
if len(email) > 254:
return False
return True
@app.route('/user', methods=['POST'])
def create_user():
data = request.json
if not validate_email(data.get('email')):
return jsonify({'error': 'Email inválido'}), 400
# continua processamentoSecrets management
Gerenciamento de segredos refere-se à prática de armazenar e acessar de forma segura informações sensíveis como chaves de API, senhas de banco de dados, tokens de autenticação e certificados. Expor esses segredos em código-fonte, logs ou variáveis de ambiente não seguras pode levar a comprometimentos graves. Nunca hardcode segredos no código; em vez disso, utilize serviços como HashiCorp Vault, AWS Secrets Manager, ou variáveis de ambiente criptografadas.
Além de armazenar segredos de forma segura, é importante rotacioná-los periodicamente e limitar o acesso apenas a quem precisa. Use o princípio do menor privilégio: cada serviço deve ter acesso apenas aos segredos necessários. Em ambientes de desenvolvimento, evite usar os mesmos segredos da produção. Ferramentas de CI/CD devem injetar segredos de forma segura, sem expô-los em logs. Considere também o uso de criptografia em repouso e em trânsito para proteger os segredos.
// Exemplo de acesso a segredo via variável de ambiente (Node.js)
const dbPassword = process.env.DB_PASSWORD;
if (!dbPassword) {
throw new Error('DB_PASSWORD não definido');
}
// Conecta ao banco usando dbPasswordBoas práticas e observações finais
Além dos quatro pilares abordados, outras práticas incluem: usar HTTPS obrigatoriamente, implementar logging e monitoramento, manter dependências atualizadas, e realizar testes de segurança periódicos. A segurança de APIs é um processo contínuo, não uma configuração única. Sempre revise e atualize suas medidas de segurança conforme novas ameaças surgem.
Referências
- OWASP API Security Project
- JWT Introduction
- OAuth 2.0
- Rate Limiting with Redis
- HashiCorp Vault
- AWS Secrets Manager
- OWASP Input Validation Cheat Sheet
Exercícios
Explique a diferença entre autenticação e autorização no contexto de APIs REST.
✓ Resposta: Autenticação verifica a identidade do cliente (quem é), enquanto autorização determina quais recursos ou ações o cliente autenticado pode acessar (o que pode fazer). Por exemplo, um token JWT autentica o usuário, e as permissões no token autorizam acesso a endpoints específicos.Cite três estratégias comuns de rate limiting e como elas funcionam.
✓ Resposta: 1. Token bucket: um balde com tokens que se renovam a cada intervalo; cada requisição consome um token. 2. Sliding window: conta requisições em uma janela de tempo deslizante. 3. Fixed window: contagem por janela fixa (ex.: 100 requisições por minuto).Por que a validação de entrada deve ser feita no servidor, mesmo que o cliente já valide?
✓ Resposta: A validação no cliente é facilmente contornada (ex.: desabilitando JavaScript ou usando ferramentas como curl). A validação no servidor é a última linha de defesa contra dados maliciosos ou malformados, garantindo que apenas dados seguros sejam processados.Descreva uma maneira segura de armazenar e acessar uma chave de API em um serviço back-end.
✓ Resposta: Nunca hardcode a chave. Use um gerenciador de segredos como AWS Secrets Manager ou HashiCorp Vault para armazená-la criptografada. No código, leia a chave via chamada à API do gerenciador ou variável de ambiente injetada de forma segura (ex.: em contêineres). Evite expor a chave em logs ou mensagens de erro.Qual a importância de usar HTTPS em APIs REST? Mencione pelo menos dois benefícios de segurança.
✓ Resposta: HTTPS criptografa a comunicação entre cliente e servidor, protegendo contra ataques man-in-the-middle (MITM) e garantindo confidencialidade dos dados (ex.: tokens, senhas). Além disso, autentica o servidor, evitando que o cliente se conecte a um servidor falso.