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:
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
Implementando a Chave de Acesso da APIGarantindo a segurança no NextJSImplementando o Rate LimiterRoadmap e próximos passosConclusão
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