DEV Community

Cover image for OpenAI or Gemini key in your frontend? Move it to an Edge Function
Ahmed Hassan Yaseen
Ahmed Hassan Yaseen

Posted on Originally published at stacksmesh.com AI-assisted

OpenAI or Gemini key in your frontend? Move it to an Edge Function

If your web app talks to OpenAI or Gemini from the browser, the API key is part of the JavaScript every visitor downloads. Anyone can copy it out and send requests that are billed to your account. Here's the order I fix it in: keep the key as a server secret, call the provider from an Edge Function that checks who is asking, then swap the leaked key for a new one.

If you got here by searching, it was probably one of these: OpenAI API key exposed in frontend, dangerouslyAllowBrowser, VITE_OPENAI_API_KEY, Gemini API key exposed in client-side code, "how to hide an API key in Lovable".

Why the key ends up public

  • The AI call lives in a component. To get the feature working in preview, the builder calls OpenAI or Gemini directly from React, so the key ships with every page load.
  • It sits in a VITE_ or NEXT_PUBLIC_ variable. Vite inlines every VITE_ variable into the client bundle at build time; Next.js does the same with NEXT_PUBLIC_. Whatever file it came from, the value is public.
  • dangerouslyAllowBrowser is on. The OpenAI JS library won't run in a browser without it, and its docs say the option exposes your secret credentials.
  • The key was pasted into a prompt or config file, and the builder wrote it into frontend code.

Confirm it

  1. Run the free Launch Check on the live site. It looks for OpenAI, Anthropic and Gemini keys in the JavaScript your site serves, and never displays the key.
  2. DevTools → Network, then use the AI feature. A request going straight to api.openai.com or generativelanguage.googleapis.com means the key is in the browser.
  3. Grep the code for sk-proj-, sk-ant-, AIza, dangerouslyAllowBrowser, and any VITE_ variable holding a key.
  4. Check the provider's usage page for requests or spend you don't recognise.

Fix 1: move the call into an Edge Function

  • Store the key as a secret, never under a VITE_ name. In Lovable: More > Cloud > Secrets. In your own Supabase project: supabase secrets set OPENAI_API_KEY=....
  • Write an Edge Function that only accepts signed-in users, validates input, and reads the key with Deno.env.get. For Gemini, call the generateContent endpoint the same way, with the key in the x-goog-api-key header.
  • In the page, call the function with supabase.functions.invoke and remove both the key and the provider SDK from the frontend.
// supabase/functions/ai-reply/index.ts
// The key lives in Edge Function secrets, never in the app.
import { withSupabase } from 'npm:@supabase/server@1';

export default {
  // auth: 'user' refuses callers who are not signed in.
  // CORS and preflight requests are handled for you.
  fetch: withSupabase({ auth: 'user' }, async (req) => {
    const { prompt } = await req.json();
    if (typeof prompt !== 'string' || prompt.length > 4000) {
      return Response.json({ error: 'Invalid prompt' }, { status: 400 });
    }

    const res = await fetch('https://api.openai.com/v1/responses', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
        'Content-Type': 'application/json',
      },
      // Use any model your project has access to.
      body: JSON.stringify({ model: 'gpt-5-mini', input: prompt }),
    });
    if (!res.ok) {
      return Response.json({ error: 'AI request failed' }, { status: 502 });
    }
    return Response.json(await res.json());
  }),
};

// In the page: no key and no OpenAI SDK, just the function.
const { data, error } = await supabase.functions.invoke('ai-reply', {
  body: { prompt },
});
Enter fullscreen mode Exit fullscreen mode

Fix 2: replace the leaked key

Do this only after the function is live, or the new key leaks the same way.

  • OpenAI: create a new key on the project's API keys page, save it as the server secret, then delete the leaked one.
  • Gemini: create a new key in Google AI Studio (keys created there since 28 May 2026 are auth keys, limited to the Gemini API by default), save it as the secret, then delete the old key. Restrict any key you keep to the Gemini API.
  • Deleting a key is final, so move every function and server to the new key first.

Fix 3: limit the damage meanwhile

  • Set a budget alert in the provider's billing settings so unexpected spend reaches you the same day.
  • Cap requests per user inside the function (for example a daily count stored in a table) so one account can't drain the budget.
  • Note any usage you don't recognise, with dates, in case you need to raise it with the provider.

Prove it worked

  • Launch Check again: no AI keys found.
  • With DevTools open, the browser calls your function, never the provider.
  • Calling the function while signed out is refused.
  • The provider dashboard shows the leaked key deleted.

Keep it from coming back

  • Provider keys live only in server secrets: never in VITE_ / NEXT_PUBLIC_, never with dangerouslyAllowBrowser.
  • When you ask the builder for an AI feature, ask for it "through an Edge Function, with the key stored in Secrets" from the first prompt.
  • Turn on GitHub secret scanning, and run the Launch Check after every publish.

Quick answers

Can an env variable hide the key? Not in the frontend. Vite writes VITE_ values into the JavaScript at build time. Only a server or Edge Function keeps it secret.

Is a Gemini key safe if it's restricted to my domain? Not for a key that can spend money. A website restriction checks the Referer header, which a script outside a browser can set to anything. Google's guidance is to call the Gemini API from your server.

Does Lovable's built-in AI have this problem? Not by design: it uses LOVABLE_API_KEY, a secret only Edge Functions can read, and Secrets offers a Rotate action for it. The risk is a key you add yourself and call from the page.

Will the provider disable a leaked key for me? Don't count on it. Until a provider acts, every request made with your key is billed to you.

Sources: OpenAI API authentication, OpenAI JavaScript library: browser use, Google: Using Gemini API keys, Vite: Env variables and modes, Lovable: Secrets, Supabase: Edge Function secrets, Supabase: Securing Edge Functions.


I first wrote this up for our fix library at StacksMesh: OpenAI or Gemini API key exposed in your app's code, with links to every official source. If you want to check your own live app for exposed keys, the free Launch Check does it in about a minute.

I wrote this with AI assistance and reviewed it myself.

Top comments (0)