How to prevent leaking API keys in frontend apps
If a string is bundled into JavaScript that ships to the browser, you must assume it is public. Minification and “don’t document it” do not hide it. Tools exist to search bundles for high-entropy strings; users can open DevTools → Sources or download the built chunk.
This article is framework-oriented but the rule is universal: server secrets stay on the server.
1. The non-negotiable rule
Never put server-side API keys, database URLs, or signing secrets in:
NEXT_PUBLIC_*(Next.js)VITE_*(Vite)- Create React App
REACT_APP_*legacy prefix - Any variable your build tool inlines into client JS
Next.js documents which env vars are exposed: Environment Variables — behavior evolves; always verify your major version.
2. What can live in public env vars?
Only non-secret configuration:
- Public site URL
- Analytics IDs meant to be visible (still consider abuse)
- Feature flags that are not security boundaries
When in doubt, treat it as secret and keep it server-only.
3. Safe architecture: Browser → Your backend → Third party
Browser --(session cookie / JWT)--> Your API route / server
Your server --(SECRET_API_KEY)--> Stripe / OpenAI / etc.
The third-party secret never appears in the browser. OWASP’s cheat sheet aligns: do not embed secrets in client-side code (Secrets Management).
Next.js App Router pattern (conceptual)
Use Route Handlers or Server Actions that run on the server only:
// app/api/chat/route.ts — server-only; do NOT prefix OPENAI_API_KEY with NEXT_PUBLIC_
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const key = process.env.OPENAI_API_KEY;
if (!key) {
return NextResponse.json({ error: "Server misconfigured" }, { status: 500 });
}
// call upstream with key — never return key to client
return NextResponse.json({ ok: true });
}
Never return process.env or log env in client components.
4. Vite + SPA (no built-in server)
Pure SPAs need a backend somewhere:
- Serverless functions (Netlify, Vercel, Cloudflare Workers) as BFF (“backend for frontend”).
- Same-origin API under
/apiproxied in dev.
If you only have static hosting, you cannot safely hold a private OpenAI key — use a tiny server component.
5. Common leak paths beyond env prefixes
| Mistake | Why it leaks |
|---|---|
fetch to third party from browser with Authorization header | Visible in Network tab |
| Embedding Google Maps or similar keys without HTTP referrer restrictions | Abuse / quota theft — use provider key restrictions |
Committing .env.local “just once” | Git history — incident guide |
Logging import.meta.env or process.env in client | Console exposure |
6. Local development
Keep OPENAI_API_KEY (etc.) in a gitignored .env.local or in PassStore and paste into server env — same rules as where to store .env.
7. Soft CTA
Developer secrets belong in a macOS vault with encrypted storage, not in src/. Download PassStore · Security model.