
Segurança em APIs: o checklist técnico que evita 90% dos incidentes
APIs são a superfície de ataque número um de aplicações modernas — a OWASP mantém um Top 10 específico para elas desde 2019, atualizado em 2023. A boa notícia: a esmagadora maioria dos incidentes explora uma lista curta de falhas conhecidas, todas evitáveis com práticas estabelecidas. Este artigo percorre esse checklist com implementações concretas.
OWASP API Security Top 10 (2023) resumido
| # | Falha | O que é em uma frase |
|---|---|---|
| 1 | Broken Object Level Authorization (BOLA/IDOR) | Usuário acessa objeto de outro trocando o ID na URL |
| 2 | Broken Authentication | Login, tokens ou recuperação de senha mal implementados |
| 3 | Broken Object Property Level Authorization | Expõe ou permite alterar campos que não devia (mass assignment) |
| 4 | Unrestricted Resource Consumption | Sem rate limit nem limites de payload — DoS e conta de nuvem explodindo |
| 5 | Broken Function Level Authorization | Endpoint admin acessível por usuário comum |
| 6 | Server-Side Request Forgery (SSRF) | API busca URL fornecida pelo usuário e alcança a rede interna |
| 7 | Security Misconfiguration | Debug ligado, CORS *, headers faltando, credenciais default |
| 8 | Lack of Protection from Automated Threats | Bots abusando de fluxos legítimos (cupons, cadastro) |
| 9 | Improper Inventory Management | Endpoints v1 antigos e esquecidos ainda no ar |
| 10 | Unsafe Consumption of APIs | Confiar cegamente em resposta de API de terceiro |
BOLA/IDOR: a falha nº 1 e a mais explorada
BOLA acontece quando a API verifica se você está logado, mas não se o recurso pedido é seu. GET /api/pedidos/12345 autenticado retorna o pedido 12345 de qualquer pessoa. A correção é simples e não negociável: toda consulta filtra pelo dono autenticado — nunca confie apenas no ID recebido.
// ERRADO — qualquer usuário logado lê qualquer pedido
const pedido = await db.query("SELECT * FROM pedidos WHERE id = $1", [req.params.id]);
// CERTO — o filtro de propriedade faz parte da query
const pedido = await db.query(
"SELECT * FROM pedidos WHERE id = $1 AND usuario_id = $2",
[req.params.id, req.usuarioAutenticado.id]
);
if (!pedido) return res.status(404).json({ erro: "não encontrado" });Retorne 404 (não 403) quando o recurso existe mas não pertence ao usuário. Um 403 confirma ao atacante que aquele ID existe — informação útil para enumeração.
Autenticação: JWT com rotação de refresh token
A arquitetura consolidada para APIs: access token de vida curta (5–15 min) + refresh token de vida longa com rotação — cada uso do refresh emite um novo e invalida o anterior. Se um refresh token vazado for usado, a reutilização é detectável e a cadeia inteira é revogada.
- Access token: JWT assinado (RS256 para microsserviços, HS256 aceitável em monólito), expiração curta.
- Refresh token: opaco, armazenado com hash no banco, rotacionado a cada uso, cookie httpOnly + Secure + SameSite.
- Nunca armazene tokens em localStorage em apps web sensíveis: qualquer XSS os rouba. Cookie httpOnly não é lido por JavaScript.
- Senhas: Argon2id (ou bcrypt com custo ≥ 12). Nunca MD5/SHA puro.
Rate limiting e limites de recurso
// Rate limit por IP + por usuário, com respostas claras
import rateLimit from "express-rate-limit";
const limiterLogin = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
limit: 10, // 10 tentativas de login por IP
standardHeaders: true, // RateLimit-* headers (draft IETF)
legacyHeaders: false,
message: { erro: "muitas tentativas; tente novamente em 15 minutos" },
});
app.use("/auth/login", limiterLogin);
// Limite de tamanho de payload: protege contra DoS de corpo gigante
app.use(express.json({ limit: "100kb" }));Validação de input e mass assignment
Duas regras que se complementam: (1) valide tudo que entra com schema explícito (Zod, Joi, JSON Schema) — tipo, formato, tamanho máximo; (2) nunca passe o body inteiro para o ORM. Mass assignment é o clássico: o cliente envia { "nome": "Ana", "role": "admin" } e um update ingênuo persiste os dois campos.
import { z } from "zod";
// Schema define exatamente o que pode entrar — nada mais
const AtualizarPerfil = z.object({
nome: z.string().min(1).max(120),
bio: z.string().max(500).optional(),
});
app.patch("/usuarios/me", async (req, res) => {
const dados = AtualizarPerfil.parse(req.body); // lança se houver campo extra/inválido
// role, plano, saldo etc. simplesmente não existem aqui
await db.usuario.update({ where: { id: req.usuario.id }, data: dados });
res.status(204).end();
});Headers, CORS e logging
- CORS com allowlist explícita de origens — nunca Access-Control-Allow-Origin: * em API autenticada por cookie.
- Headers de segurança: Strict-Transport-Security, X-Content-Type-Options: nosniff, Content-Security-Policy. Bibliotecas como helmet aplicam o conjunto.
- Logging de segurança sem segredos: registre tentativas de auth, 401/403 e mudanças de permissão — nunca tokens, senhas ou payloads completos com PII.
- Inventário: documente os endpoints ativos (OpenAPI) e desligue versões antigas. Endpoint esquecido não patcheado é porta aberta.
Conclusão
Segurança de API não é produto que se compra, é disciplina de implementação: autorização no nível do objeto em toda query, tokens de vida curta com rotação, schemas de validação estritos, rate limit e inventário vivo dos endpoints. Cubra o Top 10 da OWASP com testes automatizados de autorização — incluindo o teste de 'usuário A não acessa recurso do usuário B' em todo endpoint com ID — e você elimina a fatia que concentra os incidentes reais.
Perguntas frequentes
+JWT em localStorage ou cookie?
Para aplicações web, cookie httpOnly + Secure + SameSite=Lax é a recomendação: o JavaScript não lê o cookie, neutralizando roubo via XSS. localStorage é conveniente, mas qualquer script injetado exfiltra o token. Em mobile/CLI sem navegador, armazenamento seguro do SO (Keychain/Keystore).
+API keys são suficientes para autenticar integrações?
Para server-to-server de baixo risco, sim — com rotação, escopo mínimo e tráfego apenas por TLS. Para qualquer coisa que represente um usuário ou acesse dados sensíveis, prefira OAuth 2.0/OIDC com tokens de vida curta.
+Como testar se minha API tem BOLA?
Automatize: crie dois usuários de teste, autentique como usuário A e tente ler/alterar os IDs de recursos do usuário B em cada endpoint. Ferramentas de fuzzing e o OWASP ZAP ajudam, mas o teste de autorização por objeto é o que pega a falha nº 1.
+GraphQL muda alguma coisa nesse checklist?
Tudo se aplica, mais dois cuidados: limite profundidade e complexidade de queries (DoS por query aninhada) e desligue introspection em produção se ela não for necessária. A autorização por objeto continua sendo a falha mais comum — em resolvers.
Fontes consultadas
- OWASP API Security Top 10 (2023)
- OWASP Cheat Sheet — REST Security
- OWASP Cheat Sheet — JWT
- RFC 9700 — Best Current Practice for OAuth 2.0 Security
Revisão editorial: publicado em . Última revisão em . Conteúdo educativo, sem patrocínio das ferramentas citadas.
Leia também

Proteger senhas, contas e projetos: hashing, gerenciadores e rotação de segredos
Do gerenciador pessoal ao armazenamento correto de senhas em aplicações: Argon2, políticas modernas do NIST, passkeys, gestão de segredos e rotação de chaves.

Segurança digital para quem trabalha online: ameaças reais e defesas práticas
Modelo de ameaças para profissionais remotos: phishing, contas comprometidas, dispositivos, redes públicas, backup e resposta a incidentes — com passos verificáveis.

LGPD para Desenvolvedores: Privacidade por Design no Código
Como implementar a Lei Geral de Proteção de Dados em aplicações: consentimento, minimização, pseudonimização, direitos do titular, registros de operações e boas práticas de segurança da informação.