ExpressJS
NodeJS
Auth

Melhorando a segurança: implementando chave de acesso e rate limit na API Node.js com Express

Usei variáveis de ambiente e express-rate-limit pra reforçar a segurança da minha API Node.js — e o resultado foi um backend bem mais tranquilo de manter.

Última atualização realizada em:
Imagem do autor do blog Yago Marinho
Por Yago Marinho
Em um cenário onde mais de 35% do tráfego da web é composto por bots maliciosos, proteger uma aplicação, mesmo que simples, é essencial. Neste artigo, compartilho como implementei duas pequenas, mas importantes melhorias de segurança no meu projeto Notion Blog, utilizando uma chave de acesso via variável de ambiente e um rate limiter com Express.

Tabela de Conteúdos

💡
Você pode conferir o projeto completo no repositório do projeto

Implementando a Chave de Acesso da API

A chave de acesso foi o primeiro passo para reforçar a camada de segurança.
Ela funciona como um “cartão de entrada”: toda requisição à API precisa apresentar um token válido no cabeçalho Authorization.
Se o token estiver ausente ou incorreto, a requisição é rejeitada com um status de 401 (não autorizado).
A validação é simples, mas eficaz: o servidor compara o token recebido com o valor definido na variável de ambiente API_ACCESS_TOKEN.
Essa é uma implementação de chave de acesso simplificada, apenas para garantir que apenas usuários e aplicações desejados tenham acesso ao conteúdo disponível na API.
const apiKeyAuthorization = Middleware( request => async (): Promise<any> => { const auth = request.headers.authorization if (!auth) return Response({ status: 401, body: { message: 'API Access Token is missing' }, }) const [, token] = auth.split(' ') if (!token) return Response({ status: 401, body: { message: 'API Access Token is missing' }, }) if (token !== process.env.API_ACCESS_TOKEN) return Response({ status: 401, body: { message: 'API Access Token is invalid' }, }) return Next({ request }) }, ) // implementação das rotas export const routes: Route[] = [ Route({ path: '/posts', handler: handlerPipe(apiKeyAuthorization, getPostListHandler), env: { covers }, }), Route({ path: '/post/:slug', handler: handlerPipe(apiKeyAuthorization, getPostDataHandler), env: { covers, posts }, }), Route({ method: 'post', path: '/posts/webhooks/notion', handler: handlerPipe(verifySignatureMiddleware, triggerWebhookHandler), env: { timer }, }), ]
Essa estratégia garante que apenas quem possui a chave correta — normalmente o próprio frontend da aplicação — consiga interagir com a API.
Embora simples, ela impede acessos anônimos e ajuda a evitar o consumo indevido de recursos.

Garantindo a segurança no NextJS

Como o frontend do projeto foi desenvolvido em Next.js, tomei cuidado para garantir que o token de acesso não fosse exposto no bundle do código do client (frontend).
Para isso, todas as funções responsáveis por consumir a API foram implementadas com a diretiva server-only, que assegura que o código rode exclusivamente no backend do Next.js, e também sem a utilização do prefixo NEXT_PUBLIC.
Com isso, o token permanece protegido no servidor, nunca sendo incluído no bundle JavaScript que chega ao navegador.
Essa pequena decisão elimina o risco de vazamento acidental de credenciais e reforça o isolamento entre o cliente e o servidor.
Implementando os serviços app NextJS com a chave de acesso para garantir a autorização de acesso. Lembrando que as funções de serviço rodam apenas no backend.
import "server-only" // NextJS only export async function getPostList() { const headers = new Headers() headers.set('Authorization', bearer(process.env.API_ACCESS_TOKEN!)) const result = await fetch(`${config.api.baseUrl}/posts`, { cache: 'force-cache', headers, }) let posts: Cover[] = [] if (result.ok) posts = await result.json() return posts }
Essa estratégia é simples mas abre uma oportunidade de melhoria e avanço na aplicação. Quando olhamos para o futuro, podemos pensar num módulo de criação de chaves de API parametrizadas, o que tornará a aplicação ainda mais segura e escalável.

Implementando o Rate Limiter

A segunda melhoria de segurança foi o controle de taxa de requisições, também conhecido como rate limit.
Ele serve para evitar abusos, como scripts automatizados que fazem centenas de chamadas à API em poucos segundos.
Utilizando a biblioteca ‘express-rate-limit’, esse middleware facilita muito a implementação, bastando configurar apenas alguns parâmetros básicos e tudo fica pronto
import { rateLimit } from 'express-rate-limit' app.use(limiter()) function limiter() { return rateLimit({ windowMs: 60 * 1000, // 1 minuto de janela de permissão limit: 5, // apenas 5 chamadas por minuto por cada IP standardHeaders: true, legacyHeaders: false, handler: (_, response) => { // Retornando uma resposta JSON para facilitar o consumo response.status(429).json({ status: 'error', message: 'To many requests. Please try again later', }) }, }) }
Defini uma janela de tempo de um minuto e limitei o número máximo de requisições para cinco por IP nesse intervalo.
Quando o limite é atingido, o servidor responde com o status 429 (Too Many Requests) e uma mensagem informando que o usuário deve tentar novamente mais tarde.
Essa implementação é leve, direta e adiciona uma camada extra de proteção sem comprometer a performance da aplicação.

Roadmap e próximos passos

  • Melhorar o design para refletir um blog mais moderno e consistente.
  • Adicionar scripts de monitoramento de performance do conteúdo.
  • Utilizar uma ferramenta de bundle para otimizar o tamanho da aplicação dentro do monorepo.
  • Refatorar o código para desacoplar módulos e preparar a aplicação para futuras substituições sem grandes impactos.

Conclusão

Essas pequenas melhorias — a chave de acesso e o rate limiter — trouxeram um ganho significativo de segurança ao projeto.
Mais do que proteger endpoints, elas demonstram uma mentalidade de engenharia responsável, onde a preocupação com segurança e boas práticas acompanha o crescimento da aplicação.
Ao final, o que realmente importa é o que essas decisões comunicam:
que existe cuidado, intenção e maturidade no desenvolvimento de cada parte do sistema.
Para mim, esse é o tipo de detalhe que faz um código simples se transformar em um projeto profissional — e que mostra a qualquer profissional que segurança e qualidade não são opcionais, mas princípios.
💡
Você pode conferir o projeto completo no repositório do projeto