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.

Como ocultar uma chave de API no JavaScript do frontend sem vazá-la
Cristian Da Conceicao
Fundador do Picasso IA

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

Desenvolvedor inspecionando a aba de rede do navegador em um monitor grande em um escritório iluminado

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.

FrameworkPrefixo que vai para o públicoO que acontece
ViteVITE_O valor é embutido no pacote
Next.jsNEXT_PUBLIC_O valor é embutido no código do cliente
Create React AppREACT_APP_O valor é embutido no momento do build
NuxtNUXT_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

Desenvolvedor preocupado apoiando a mão na testa tarde da noite em frente a um notebook

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 vazadaAbuso típicoO que custa a você
Token de LLMChatbots gratuitos, geração de spamConta de tokens, bloqueio por limite de requisições
Token de geração de imagem ou vídeoRenderizações em massa, revendaConta de uso de GPU
Token de mapas ou buscaRaspagem em grande escalaEsgotamento de cota
Segredo de banco de dados ou armazenamentoLeitura ou exclusão de registrosVazamento 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

Vista de cima de uma mão desenhando um diagrama de arquitetura com três caixas em um caderno

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:

  1. O segredo fica apenas nas variáveis de ambiente do servidor. Nunca no repositório, nunca em uma variável no estilo NEXT_PUBLIC_.
  2. 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.
  3. 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

Vista ampla de baixo para cima de um corredor de racks de servidores em um data center

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.

// worker.js
const UPSTREAM =
  "https://api.picassoia.com/v1/models/picassoia/picassoia-image/predictions";

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (request.method !== "POST" || url.pathname !== "/api/generate") {
      return new Response("Not found", { status: 404 });
    }

    const { prompt } = await request.json().catch(() => ({}));
    if (typeof prompt !== "string" || prompt.length === 0 || prompt.length > 500) {
      return new Response("Bad request", { status: 400 });
    }

    const upstream = await fetch(UPSTREAM, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${env.PICASSOIA_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ input: { prompt } }),
    });

    const data = await upstream.json();
    return Response.json(
      { id: data.id, status: data.status },
      { status: upstream.status }
    );
  },
};

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

Catraca de aço inoxidável em um saguão de escritório moderno com um visitante passando

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 endpointLimite inicialMotivo
Geração de texto20 requisições por minuto por usuárioBarato por chamada, fácil de fazer spam
Geração de imagem5 requisições por minuto por usuárioCusto real de GPU em cada chamada
Geração de vídeo2 requisições por minuto por usuárioLenta e cara
Consultas de status somente leitura60 requisições por minuto por usuárioConsultas 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

Close macro de uma mão segurando um anel de metal desgastado feito de peças de latão

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

Engenheiro desenhando um fluxo de três etapas com setas em um quadro branco de vidro em uma sala de reuniões

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:

  1. O usuário faz login no seu backend.
  2. 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.
  3. O navegador usa esse token temporário diretamente na requisição pesada.
  4. 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

Dois desenvolvedores trabalhando lado a lado em uma mesa compartilhada com um cofre de aço entre eles

Se um token ficou público mesmo por uma hora, presuma que alguém o copiou. Siga esta lista na ordem:

  1. Revogue ou rotacione o token no painel do provedor agora mesmo.
  2. Implante o substituto apenas nas variáveis de ambiente do servidor.
  3. Leia os logs de uso do período de exposição e procure IPs, modelos ou picos desconhecidos.
  4. Aperte os limites de gasto antes de fazer qualquer outra coisa.
  5. 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:

  1. 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.
  2. Abra a página do modelo. Acesse Claude Sonnet 5 no PicassoIA.
  3. 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.
  4. 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."
  5. 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

Designer sorridente em uma mesa de estúdio iluminado olhando para um monitor cheio de fotografias

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.

Compartilhe este artigo

Escolha seu idioma