Como ocultar uma chave de API no JavaScript do frontend sem vazá-la
O código do navegador é público por natureza, então qualquer chave de API que você envie em um pacote React, Vue ou JavaScript puro pode ser copiada em segundos. Este artigo mostra como um pequeno proxy no servidor, limites de requisições, tokens restritos e rotação rápida mantêm suas credenciais fora do alcance.
Abra qualquer site que você construiu no mês passado, pressione F12 e clique na aba Network. Todo cabeçalho que seu JavaScript enviou está ali, em texto puro, incluindo o valor Authorization que você tinha certeza de que ninguém encontraria. Essa é a verdade incômoda por trás de como ocultar uma chave de API no JavaScript do frontend: lá você não consegue escondê-la. O que você pode fazer é parar de enviar o segredo para o navegador e deixar um servidor sob seu controle fazer a chamada em nome dos seus usuários.
Este artigo mostra exatamente como isso funciona. Você vai ver por que os bundlers vazam variáveis de ambiente, como robôs encontram tokens em minutos após um deploy e como criar um pequeno proxy em Express ou no Cloudflare Workers que mantém sua credencial no servidor. Depois, acrescentamos limites de requisições, validação de entrada, tokens restritos e um plano de resposta claro para o dia em que algo vazar mesmo assim.
💡 Resposta curta: se um segredo vai junto com um código que roda no navegador, ele é público. Oculte-o movendo a requisição para um backend, e não codificando, dividindo ou embaralhando a string.
Por que o código do frontend não consegue guardar segredos
Um navegador funciona baixando o seu código e executando-o na máquina do visitante. Tudo o que ele baixa, o visitante pode ler: HTML, CSS, pacotes JavaScript, source maps e cada requisição que o seu código faz. Nenhuma configuração, flag ou etapa de build torna invisível uma string para quem está executando o computador.
Tudo é legível no navegador
Três lugares expõem um token sem que seja preciso nenhuma habilidade de hacker:
A aba Network. Cada requisição lista sua URL, cabeçalhos e payload. Um token Bearer em um cabeçalho está a um clique de distância.
A aba Sources. Seu pacote está ali, e, com source maps habilitados, seus arquivos originais também, com os comentários.
Ver código-fonte e curl. Qualquer pessoa pode baixar seu pacote e executar grep para prefixos de token como sk_ ou pia_sk_.
Os bundlers embutem suas variáveis
Uma confusão comum acontece assim: "Coloquei no arquivo .env, então é privado". O arquivo .env é privado. O que o seu bundler faz com ele é outra história. Vite, Next.js e Create React App substituem variáveis com prefixos específicos pelos seus valores literais durante o build.
Framework
Prefixo que vai para o público
O que acontece
Vite
VITE_
O valor é embutido no pacote
Next.js
NEXT_PUBLIC_
O valor é embutido no código do cliente
Create React App
REACT_APP_
O valor é embutido no momento do build
Nuxt
NUXT_PUBLIC_
O valor vai para a configuração de runtime pública
Assim, VITE_PROVIDER_TOKEN=abc123 em um arquivo .env termina como a string literal "abc123" dentro de assets/index-xxxx.js. Variáveis sem o prefixo público ficam fora do pacote do cliente, e é exatamente por isso que o segredo pertence ao lado do servidor.
Ofuscação só atrasa
Base64, divisão de strings, caracteres invertidos, truques com XOR: nada disso funciona, porque o seu código precisa reconstruir o valor real antes de enviar a requisição. Quando a requisição sai, a aba Network mostra o resultado final. A ofuscação dá a um atacante dez minutos de leve incômodo e dá a você uma dor de cabeça de manutenção permanente.
Como os vazamentos acontecem de fato
Robôs varrem repositórios e pacotes
O método mais rápido também é o mais monótono: abrir a página, acionar a funcionalidade e ler o cabeçalho. Sem scripts. Scanners automatizados vão além. Eles rastreiam repositórios públicos, pacotes npm e sites no ar em busca de formatos de token conhecidos, e muitos provedores usam prefixos reconhecíveis (sk_, ghp_, pia_sk_) justamente para que os scanners consigam identificá-los.
Um token enviado a um repositório público no GitHub pode ser capturado em minutos. Alguns provedores fazem a varredura e revogam automaticamente, o que é um bônus agradável, mas não é um plano.
Quanto um vazamento custa de fato
Em APIs de pagamento por uso, a conta é o dano visível. O dano oculto é pior: cotas esgotadas que derrubam o seu próprio app, abuso sinalizado na sua conta e, com permissões frouxas, acesso a dados reais.
Credencial vazada
Abuso típico
O que custa a você
Token de LLM
Chatbots gratuitos, geração de spam
Conta de tokens, bloqueio por limite de requisições
Token de geração de imagem ou vídeo
Renderizações em massa, revenda
Conta de uso de GPU
Token de mapas ou busca
Raspagem em grande escala
Esgotamento de cota
Segredo de banco de dados ou armazenamento
Leitura ou exclusão de registros
Vazamento de dados
Apps de IA são o alvo preferido. Modelos como GPT 5.6 Luna ou Claude Sonnet 5 cobram por token, então uma credencial roubada se transforma diretamente em computação gratuita para outra pessoa, às suas custas.
Coloque um proxy entre o navegador e a API
A solução é arquitetural. Em vez de o navegador chamar o provedor diretamente, o navegador chama o seu servidor, e o seu servidor chama o provedor.
Browser -> POST /api/generate -> Your server -> Provider API
(holds the secret)
Três regras mantêm esse desenho honesto:
O segredo fica apenas nas variáveis de ambiente do servidor. Nunca no repositório, nunca em uma variável no estilo NEXT_PUBLIC_.
O navegador envia apenas a entrada do usuário. Um prompt, um ID, uma escolha de uma lista. Nunca uma URL, um cabeçalho ou um nome de modelo que ele escolha livremente.
O servidor decide o que é permitido. Ele valida a entrada, anexa a credencial, encaminha a chamada e devolve apenas os campos que a página precisa.
Um proxy Express que funciona
Este exemplo encaminha uma requisição de imagem para a API do PicassoIA, que autentica com um token Bearer no cabeçalho Authorization e expõe as previsões em /v1/models/{owner}/{name}/predictions. O mesmo formato funciona para qualquer outro provedor.
// server.js
import express from "express";
import rateLimit from "express-rate-limit";
const app = express();
app.use(express.json({ limit: "20kb" }));
app.use("/api/", rateLimit({ windowMs: 60_000, limit: 10 }));
const UPSTREAM =
"https://api.picassoia.com/v1/models/picassoia/picassoia-image/predictions";
app.post("/api/generate", async (req, res) => {
const { prompt } = req.body ?? {};
if (typeof prompt !== "string" || prompt.length === 0 || prompt.length > 500) {
return res.status(400).json({ error: "Prompt must be 1 to 500 characters." });
}
try {
const upstream = await fetch(UPSTREAM, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PICASSOIA_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ input: { prompt } }),
});
const data = await upstream.json();
// Return only what the browser needs, never the raw upstream body.
res.status(upstream.status).json({ id: data.id, status: data.status });
} catch {
res.status(502).json({ error: "Upstream request failed." });
}
});
app.listen(3000);
Inicie com node --env-file=.env server.js (Node 20.6 ou mais recente) para que o token venha de um arquivo fora do controle de versão. A API do PicassoIA é assíncrona: você cria uma previsão e depois consulta o status dela. Adicione uma segunda rota GET /api/result/:id construída do mesmo jeito e confira os campos exatos da resposta na página da API do PicassoIA.
Versão serverless no Cloudflare Workers
Não quer manter um servidor? Um Worker faz o mesmo trabalho com menos linhas. Guarde o segredo com npx wrangler secret put PICASSOIA_TOKEN e ele nunca toca o seu repositório.
Vercel Functions, Netlify Functions e AWS Lambda seguem o mesmo padrão: uma pequena rota, um segredo nas configurações da plataforma e nenhuma credencial no código do cliente.
Código do frontend depois da correção
O navegador agora conversa apenas com a sua própria rota:
async function generate(prompt) {
const res = await fetch("/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
return res.json();
}
Faça o build do app, abra o pacote e procure nele. Não deve sobrar nada para encontrar.
Feche o proxy também
Um proxy sem limites é apenas uma forma mais conveniente de estranhos gastarem o seu dinheiro. Trate-o como um endpoint público, porque é um.
💡 Sobre CORS: definir Access-Control-Allow-Origin com o seu próprio domínio impede que outros sites chamem o seu proxy a partir do navegador de um visitante. Não adianta nada contra curl ou um script. A proteção real vem de autenticação, limites de requisições e validação.
Limites de requisições por usuário ou IP
Limite por usuário autenticado quando você tiver login, e por endereço IP quando não tiver. Atrás de um CDN ou balanceador de carga, garanta que o seu framework leia o IP real do cliente (no Express, isso significa definir trust proxy corretamente), senão todos os visitantes vão dividir o mesmo balde.
Tipo de endpoint
Limite inicial
Motivo
Geração de texto
20 requisições por minuto por usuário
Barato por chamada, fácil de fazer spam
Geração de imagem
5 requisições por minuto por usuário
Custo real de GPU em cada chamada
Geração de vídeo
2 requisições por minuto por usuário
Lenta e cara
Consultas de status somente leitura
60 requisições por minuto por usuário
Consultas repetidas são normais
Trate esses números como pontos de partida e ajuste-os com base no tráfego real.
Valide cada entrada
Nunca encaminhe o corpo da requisição do navegador como chegou. Verifique cada campo:
Limite o tamanho do prompt. O exemplo acima rejeita qualquer coisa com mais de 500 caracteres.
Crie uma lista de modelos permitidos. Deixe a página enviar "fast" ou "quality", e depois mapeie esses rótulos para os nomes reais dos modelos no servidor.
Limite o tamanho da saída. Defina um número máximo de tokens de saída ou de imagens por chamada.
Rejeite campos desconhecidos. Se o esquema diz prompt, nada além disso passa.
Defina limites de gasto no provedor
A maioria dos provedores permite definir limites mensais de gasto e alertas. Ative-os. Essa é a rede de segurança para o dia em que todas as outras camadas falharem. Use também um token separado por projeto e por ambiente, para que revogar um não derrube todos os outros.
Quando um token público é aceitável
Nem toda credencial é um segredo. Algumas são feitas para navegadores: a configuração web do Firebase, os tokens publicáveis do Stripe (os que começam com pk_) e os tokens do Google Maps JavaScript. Elas só são seguras quando você as restringe.
Restrinja por domínio e escopo
Abra o painel do provedor e aplique todas as restrições disponíveis:
Limites de referrer HTTP para que o token funcione apenas a partir de yourdomain.com.
Limites de escopo da API para que um token de Maps chame apenas o Maps e mais nada.
Cotas diárias para que uma onda de abuso bata em um teto.
Um aviso: as verificações de referrer dependem do cabeçalho Referer, e clientes que não são navegadores podem forjá-lo. As restrições reduzem o abuso casual, mas não transformam um token público em um segredo. Mantenha o valor baixo, com escopo limitado e teto de gasto.
Tokens de curta duração para navegadores
Algumas tarefas são pesadas demais para passar pelo seu servidor, como uploads grandes ou streaming em tempo real. Para elas, use uma troca de tokens:
O usuário faz login no seu backend.
Seu backend pede ao provedor um token de curta duração e escopo restrito, ou assina um JWT que expira em 5 a 15 minutos.
O navegador usa esse token temporário diretamente na requisição pesada.
O token expira sozinho, então um valor copiado logo deixa de valer.
Vários provedores de tempo real e armazenamento oferecem tokens efêmeros exatamente para esse padrão. Seu segredo de longa duração nunca sai do servidor.
O que fazer depois de um vazamento
Revogue primeiro, investigue depois
Se um token ficou público mesmo por uma hora, presuma que alguém o copiou. Siga esta lista na ordem:
Revogue ou rotacione o token no painel do provedor agora mesmo.
Implante o substituto apenas nas variáveis de ambiente do servidor.
Leia os logs de uso do período de exposição e procure IPs, modelos ou picos desconhecidos.
Aperte os limites de gasto antes de fazer qualquer outra coisa.
Avise sua equipe e verifique se o mesmo valor foi reutilizado em algum outro lugar.
Limpe o histórico do Git e os pacotes
Apagar o segredo do seu último commit não resolve nada, porque o histórico ainda o guarda. Deploys antigos, caches de CDN e source maps públicos também podem guardá-lo. A rotação é a correção real. Reescrever o histórico com git filter-repo é higiene depois disso.
Depois, torne uma repetição improvável:
Adicione um scanner de pré-commit, como o gitleaks.
Ative a proteção de push e a varredura de segredos no seu host Git.
Evite publicar source maps em produção, ou sirva-os apenas para o seu rastreador de erros.
Adicione uma etapa de CI que procure no pacote gerado prefixos de token conhecidos e falhe o build se encontrar algum.
Audite seu pacote com um LLM
Um LLM é um segundo par de olhos rápido para essa tarefa. Veja como usar o Claude Sonnet 5 no PicassoIA para encontrar vazamentos e rascunhar seu proxy:
Faça o build e escaneie primeiro. Execute npm run build e depois grep -rE "sk_|pk_|pia_sk_|Bearer " dist/ para pegar os casos óbvios você mesmo.
Cole apenas código redigido. Inclua os arquivos que fazem chamadas de rede, com cada valor real substituído por REDACTED. Nunca cole um segredo ativo em nenhuma ferramenta de chat.
Faça uma pergunta específica. Por exemplo: "Liste todos os pontos em que este código envia uma credencial a partir do navegador e reescreva cada um para chamar uma rota do servidor."
Revise a resposta com base nas regras acima. Verifique validação, limites de requisições e tratamento de erros, e depois teste no DevTools.
💡 Dica: para uma segunda opinião, execute o mesmo prompt no GPT 5.6 Sol ou obtenha uma primeira análise rápida no Gemini 3.5 Flash. Modelos diferentes pegam erros diferentes.
Crie apps de imagem sem vazar tokens
Tudo o que foi dito acima se aplica com ainda mais força quando o seu app gera imagens ou vídeos, porque cada chamada consome tempo de GPU. O padrão continua o mesmo: a página coleta um prompt, o seu proxy guarda a credencial e o PicassoIA faz a renderização.
Escolha um modelo que combine com o seu produto. O Flux 2 Pro serve para fotorrealismo detalhado, o Seedream 4.5 lida bem com prompts com muitos elementos, o P-Image é uma opção rápida para pré-visualizações, e o GPT Image 2 é forte com texto dentro das imagens. Para movimento, explore os modelos de texto para vídeo no catálogo completo de modelos do PicassoIA.
Aqui vai uma checklist rápida antes do seu próximo deploy:
Nenhuma credencial aparece no pacote gerado ou nos source maps.
O navegador chama o seu proxy, nunca o provedor.
Tamanho do prompt, escolha do modelo e tamanho da saída são validados no servidor.
Limites de requisições e um teto de gasto no provedor estão ativos.
Qualquer token público está restrito por domínio, escopo e cota.
Você conhece os passos de revogação de cada token antes de precisar deles.
Pronto para ver funcionando? Abra o PicassoIA, gere algumas imagens com os modelos acima e depois conecte o mesmo prompt ao seu próprio proxy. Experimente estilos e prompts diferentes e publique um app em que a única coisa que os visitantes podem copiar do navegador seja a imagem final.