12 min

Protegendo suas APIs Node.js: O Básico Que Quase Ninguém Faz (Mas Deveria)

Avatar image
Gregory Serrao
Esse artigo é um guia direto, prático e sem firula pra você entender e aplicar as proteções fundamentais de segurança em APIs feitas com Node.js. Se você ignorar isso aqui, alguém (em algum momento) vai explorar sua API. E não vai avisar.

Você pode estar escrevendo código limpo, usando boas práticas de arquitetura e entregando features novas toda semana… mas se sua API não estiver segura, você só está acelerando em direção ao desastre.

Segurança não é coisa só de DevOps, especialista ou projeto enterprise. É base. É fundação. E o mais insano? A maior parte das APIs que estão em produção hoje negligenciam o mínimo — o básico que qualquer backend deveria ter desde o dia 1.

1. Validação de Entrada: Confie desconfiando

A validação de entrada é a primeira linha de defesa da sua aplicação. Tudo que vem de fora — query params, body, headers — deve ser tratado como potencialmente malicioso. Não validar os dados de entrada é o equivalente a deixar qualquer um entrar no seu sistema sem mostrar identidade.

O que pode acontecer se você ignorar isso?

  • Injeção de código (SQL, NoSQL, XSS)
  • Quebra de lógica da aplicação
  • Travamentos por dados malformados

Para mitigar esse risco, utilize bibliotecas como zod ou joi para validar todas as entradas, exemplo com o zod.

import { z } from 'zod'

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(6)
})

app.post('/login', (req, res) => {
  const validationResult = schema.safeParse(req.body)

  if (!validationResult.success) {
    return res.status(400).json({
      error: 'Dados inválidos.'
    })
  }
})

Valide primeiro, processe depois. Sempre, simples assim!

2. Não exponha erros internos

Mostrar detalhes de erro (stack trace, nomes de arquivos, variáveis internas) é como dar o mapa do castelo para quem está tentando invadi-lo. Em produção, nunca exponha detalhes técnicos.

O que pode acontecer se você mostrar erros internos?

  • Exposição de estrutura interna
  • Descoberta de tecnologias usadas
  • Ataques dirigidos com base nas informações reveladas
app.use((err, req, res, next) => {
  req.logger.error(err) // Log interno
  res.status(500)
    .json({
      error: 'Internal server error'
    })
})

3. Rate Limiting: coloque limites

Sem limitar o número de requisições por IP, sua API está aberta para ataques de força bruta, scraping excessivo e negação de serviço (DoS).

O que pode acontecer sem rate limiting?

  • Derrubada do servidor por excesso de requisições
  • Tentativas automatizadas de login
  • Exploração de vulnerabilidades em massa
import rateLimit from 'express-rate-limit'

const limiter = rateLimit({
  windowMs: 60 * 1000 // 1 minuto
  max: 20 // até 20 requests por minuto por ip
})

app.use('/login', limiter) 

Priorize rotas sensíveis: login, cadastro, recuperação de senha.

4. CORS: controle quem pode acessar sua API

CORS (Cross-Origin Resource Sharing) define quais origens podem interagir com sua API. Liberar tudo (*) é como aceitar chamadas de qualquer site, inclusive páginas falsas criadas por atacantes.

O que acontece sem controle de CORS?

  • Aplicações maliciosas podem interagir com sua API em nome do usuário
  • Exposição de endpoints sensíveis
  • Risco de ataques CSRF combinados com sessões mal gerenciadas
import cors from 'cors'

const whiteList = ['codeinit.dev']

app.use(cors({
  origin: (origin, callback) => {
    if (whiteList.includes(origin)) {
      return callback(null, true)
    }

    callback(new Error('Invalid origin'))
  },
  credentials: true
}))

Lembre-se: CORS mal configurado é porta aberta.

5. Proteja seus tokens JWT

Autenticação com JWT é prática, mas também é frágil se mal usada. Um token sem criptografia adequada, armazenado de forma insegura ou com validação frouxa vira um passaporte para o sistema inteiro.

O que pode acontecer se você não proteger o JWT?

  • Sequestro de sessão
  • Acesso indevido a dados sensíveis
  • Uso de tokens expirados ou inválidos
  • Veja abaixo como verificar a integridade do JWT usando uma secret
import jwt from 'jsonwebtoken'

const verifyToken = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1]

  if (!token) {
    return res.status(401)
      .json({ 
        error: 'Unauthorized' 
      })
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    req.user = decoded
    next()
  } catch (err) {
    req.logger.error(err) // log interno
    return res.status(401)
      .json({ 
        error: 'Unauthorized' 
      })
  }
}

6. Helmet: cabeçalhos de segurança sem dor

O Helmet adiciona automaticamente cabeçalhos HTTP que protegem sua aplicação contra ataques comuns como XSS, clickjacking e sniffing de conteúdo. Ele reduz a superfície de ataque de forma passiva e eficaz.

O que acontece sem Helmet?

  • O navegador não tem diretrizes de proteção
  • Seus headers expõem sua stack desnecessariamente
  • Fica mais fácil para scripts maliciosos explorarem falhas conhecidas
import helmet from 'helmet'

app.use(helmet())

7. HTTPS: sem ele, tudo pode ser interceptado

Se sua aplicação ainda trafega dados em HTTP puro, você está entregando tudo de bandeja para qualquer atacante entre o cliente e o servidor.

O que pode acontecer?

  • Interceptação de senhas, tokens e dados pessoais
  • Ataques do tipo man-in-the-middle
  • Comprometimento total da sessão do usuário

Ative HTTPS com certificados gratuitos (Let's Encrypt) ou use Cloudflare como proxy seguro.

8. Dependências: cada pacote é um risco

A maioria dos sistemas atuais depende de dezenas (ou centenas) de pacotes de terceiros. E cada um deles pode carregar vulnerabilidades.

O que pode acontecer?

  • Um simples left-pad derruba sua stack
  • Código malicioso se infiltra em build pipelines
  • Exploração de CVEs não corrigidos
  • Utilize o comando audit para verificar vulnerabilidades nos pacotes instalados

9. Logs e Monitoramento: o que você não vê, pode te matar

Sem visibilidade, você só descobre que foi atacado quando alguém te avisa — ou quando seu banco de dados já está em outro país.

O que monitorar:

  • Falhas de autenticação
  • Padrões anômalos de acesso
  • Erros 500 frequentes

BONUS: Não confie nas suas variáveis de ambiente .env não é cofre. Variáveis de ambiente são uma forma prática de configurar sua aplicação, mas não foram feitas para garantir segurança. Tratar .env como lugar seguro é uma armadilha comum — especialmente quando você começa a colocar credenciais de banco, chaves JWT, segredos de API e tokens de terceiros ali.

O que pode dar errado?

  • Seu arquivo .env pode vazar num commit sem querer
  • Pode ser lido em um container ou ambiente mal configurado
  • Pode ser sobrescrito em produção sem que você perceba

Boas práticas:

  • Nunca envie .env para o repositório (use .gitignore com atenção)
  • Use .env.example apenas com variáveis genéricas
  • Em produção, configure as variáveis direto no ambiente, não em arquivos
  • Rotacione segredos periodicamente — como você faria com senhas

Dica: Use ferramentas como Doppler, Vault ou mesmo o Secrets Manager da AWS para armazenar variáveis críticas fora do código-fonte. E logue alertas sempre que uma variável essencial estiver ausente ou mal configurada.

Conclusão

A segurança da sua API não é opcional, nem algo pra "depois que escalar". É o pré-requisito pra não virar estatística. Cada item citado aqui cobre falhas que já derrubaram sistemas, causaram prejuízos financeiros e deixaram devs sem dormir.

Você não precisa ser paranoico, mas precisa ser disciplinado. Faça o básico — porque a maioria não faz. A diferença entre uma API segura e um desastre é a escolha de aplicar isso agora.

Cadastre-se para novos posts