How to Secure Claude API Keys in Frontend Apps
The short answer: you can't secure a key in the frontend
If your question is "how do I safely embed my Claude API key in a React app, mobile app, or browser extension," the answer is: you don't. Any key shipped to a client — whether hardcoded, pulled from an environment variable at build time, or fetched from an endpoint on page load — is visible to anyone who opens dev tools, inspects network traffic, or decompiles your app bundle. There is no obfuscation, minification, or "secret" environment variable trick that prevents this. JavaScript that runs in a browser is readable JavaScript.
This isn't a Claude-specific limitation — it's true for every API key from every provider (Anthropic, OpenAI, Stripe, AWS). The fix is always architectural, not cryptographic: never let the key leave your server. Everything below explains what that looks like in practice, and the tradeoffs between rolling your own backend and using a managed layer.
Why frontend-embedded keys get abused
Once a key is exposed client-side, here's what typically happens, in order:
- A bot or scraper harvests it from your JS bundle or network tab within hours of deployment
- It gets used to run requests against your account, often at high volume (crypto scams, spam generation, scraping)
- Your usage bill spikes, sometimes by thousands of dollars, before you notice
- You rotate the key, but the exposed version is often already cached in CDNs, browser extensions, or public GitHub mirrors of your build
Anthropic's dashboard gives you usage visibility, but it's reactive — you see the damage after it happens, not before.
Pattern 1: Backend-for-frontend (BFF) proxy
The standard fix is to put a thin server between your frontend and the Claude API. Your frontend never sees the key; it only talks to your own backend, which holds the key as a server-side secret and forwards requests.
// server.js (Node/Express) — the key lives only here
app.post('/api/chat', async (req, res) => {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': process.env.ANTHROPIC_API_KEY, // never sent to the browser
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
},
body: JSON.stringify(req.body)
});
const data = await response.json();
res.json(data);
});
// frontend — no key anywhere in this file
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ model: 'claude-sonnet-4-5', messages: [...] })
});
This is the minimum viable pattern. It works, but you still own:
- Rate limiting per user (otherwise one abusive client can exhaust your account limit)
- Auth on your
/api/chatroute so random strangers can't hit it - Logging and cost tracking per customer
- Rotating the key if it ever leaks server-side
Pattern 2: Scoped application keys instead of your root key
A proxy stops the key from reaching the browser, but it doesn't stop the problem of one key controlling your entire Anthropic account. If that server-side key leaks — through a misconfigured log, a compromised dependency, or a leaked .env file — the blast radius is everything: all your usage, all your billing, all your customers.
This is the gap most teams don't think about until it's too late. The fix is issuing scoped, revocable keys per application or per environment instead of reusing one root key everywhere.
This is where SubToAPI fits in. It sits between your Claude subscription and your product, issuing per-app keys (sub_live_...) that you can revoke individually without touching your underlying Anthropic access. You still put the key on your backend — the BFF pattern above doesn't go away — but instead of one master key with full blast radius, each app, environment, or customer gets its own key you can kill independently.
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Explain key scoping in one sentence"}]
}'
Because requests go through standard HTTPS with a bearer token, swapping it into an existing BFF proxy is a one-line change — see the quickstart and messages docs. Streaming and tool use work the same way as direct Anthropic calls, just with per-key usage metadata attached — details in the streaming and tools docs.
Pattern 3: Short-lived tokens for direct client access
If you truly need the browser to call an LLM endpoint directly (rare, but it happens for latency-sensitive streaming UIs), the pattern is: your backend issues a short-lived, scoped token on demand, the frontend uses it for one session, and it expires quickly. The long-lived provider key never leaves the server; what reaches the browser is a narrow, time-boxed credential with no reuse value if captured.
This adds real complexity (token issuance endpoint, expiry handling, refresh logic) and is usually overkill compared to just proxying through your backend with proper rate limiting.
A practical checklist
- [ ] No API key, in any form, appears in frontend JS, HTML, or mobile app binaries
- [ ] All Claude requests route through a backend you control
- [ ] Your backend route requires its own auth (session, JWT, etc.) before forwarding to Claude
- [ ] Per-user or per-IP rate limiting exists on your proxy route
- [ ] You use scoped keys per app/environment rather than one root key everywhere, so a leak is containable — see pricing for plans with team seats and per-key tracking
- [ ] Keys are stored as environment secrets (Vault, AWS Secrets Manager, platform env vars), never committed to git
- [ ] You have a rotation plan if a server-side key does leak
How do I use the Claude API from a React or Next.js frontend safely?
Never call api.anthropic.com directly from client code. Create an API route (Next.js API routes, a Node/Express endpoint, or a serverless function) that holds the key server-side and forwards requests. The frontend only talks to your own domain.
Is it safe to use environment variables for API keys in a frontend build?
No — if the variable is read into client-side JavaScript (e.g., NEXT_PUBLIC_* prefixes, Vite's import.meta.env), it ships in the compiled bundle and is visible to anyone. Only use unprefixed server-only environment variables that your build tool keeps out of client bundles.
What should I do if I accidentally exposed a Claude API key?
Rotate it immediately in your Anthropic console, check usage logs for unexpected activity, and move to a backend proxy pattern so it can't happen again. If you're managing multiple app keys, a per-app scoped setup via SubToAPI lets you revoke a single compromised key without affecting other apps.