icardb
Segurança em APIs: o checklist técnico que evita 90% dos incidentes
Voltar para artigosSEGURANÇA DIGITAL

Segurança em APIs: o checklist técnico que evita 90% dos incidentes

Por Lucas Mendes 7 min de leitura

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

#FalhaO que é em uma frase
1Broken Object Level Authorization (BOLA/IDOR)Usuário acessa objeto de outro trocando o ID na URL
2Broken AuthenticationLogin, tokens ou recuperação de senha mal implementados
3Broken Object Property Level AuthorizationExpõe ou permite alterar campos que não devia (mass assignment)
4Unrestricted Resource ConsumptionSem rate limit nem limites de payload — DoS e conta de nuvem explodindo
5Broken Function Level AuthorizationEndpoint admin acessível por usuário comum
6Server-Side Request Forgery (SSRF)API busca URL fornecida pelo usuário e alcança a rede interna
7Security MisconfigurationDebug ligado, CORS *, headers faltando, credenciais default
8Lack of Protection from Automated ThreatsBots abusando de fluxos legítimos (cupons, cadastro)
9Improper Inventory ManagementEndpoints v1 antigos e esquecidos ainda no ar
10Unsafe Consumption of APIsConfiar 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.

ts
// 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

ts
// 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.

ts
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

Revisão editorial: publicado em . Última revisão em . Conteúdo educativo, sem patrocínio das ferramentas citadas.

Crédito da imagem: Foto: Lucas Mendes / Gerado por IA (Licença Editorial)

Leia também