If you've ever built a React Native or Expo app with the Anthropic API,
you've probably done this at some point:
EXPO_PUBLIC_ANTHROPIC_API_KEY=sk-ant-...
It works. The app calls Claude. Everything is fine.
Until you realize that EXPO_PUBLIC_ means the key is bundled
into your APK and your web build — visible to anyone who downloads
the app or opens DevTools.
The fix: a server-side proxy
The solution is straightforward: never call Anthropic directly from
the client. Put a thin proxy in between.
Your app → (Bearer token) → Proxy → (Anthropic key) → Claude
Your Anthropic key never leaves the server.
I open-sourced mine
I kept building this proxy from scratch for every project, so I
cleaned it up and published it as a template:
Two flavors, same interface:
Firebase Functions — uses Firebase Auth ID tokens.
If you're already on Firebase, it's a near-zero-config drop-in.
Deploys in ~5 minutes.
Cloudflare Worker — framework-agnostic, ~0ms cold start,
100k requests/day on the free tier. Deploys in ~3 minutes.
Both are ~100 lines of vanilla JS with no dependencies
beyond the platform SDK.
Usage
const res = await fetch('https://YOUR_PROXY_URL', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
messages: [{ role: 'user', content: 'Hello!' }],
}),
});
const data = await res.json();
console.log(data.content[0].text);
The response is the raw Anthropic API response — no transformation.
What it handles
- API key stored in Secret Manager / Workers Secrets
- Auth verified on every request
- CORS locked to your origins
- Hard token cap to protect your bill
- Usage logged per user (uid + token count)
Feedback welcome — especially on the auth model for the
Cloudflare version.
Top comments (0)