Why We Won’t Put the API Key Inside the Mobile App
This guide is for clients deciding how a mobile app should talk to paid or sensitive APIs. You’ll learn why API keys inside iOS or Android apps are not actually secret, what architecture replaces that pattern, and how to decide when a backend proxy is worth the extra work.
TL;DR — We do not put a secret API key inside a mobile app because users can extract it from the app package or from network traffic, then use it outside your app. The usual fix is simple in concept: the mobile app talks to your backend, your backend stores the key securely, and only the backend calls the third-party API. Reading time: ~7 min
What it is and where it sits
An API key is a shared secret used to identify your application to another service. Think of it like a password for software. If that key is embedded inside an iPhone or Android app, it is shipped to every user device. Once it is on a user device, it is no longer secret.
That is the core reason for the policy: mobile apps are public distribution artifacts, not secret storage.
Where the key would sit if we did the unsafe version
In the unsafe design, the app contains the key in one of these places:
- hard-coded in source code
- bundled in a config file
- injected at build time into the app package
- fetched once and cached on device
Then the app calls the third-party API directly.
Mobile App -> Third-Party API
Authorization: Bearer YOUR_API_KEY
This looks simpler, but it creates a permanent leak risk. Anyone who installs the app can inspect it. Even if the key is obfuscated (made harder to read), it is still present and usable.
What replaces it
We replace direct app-to-provider calls with a small backend service, often called a server-side proxy or API gateway. "Server-side" just means it runs on infrastructure you control, not on the customer’s phone.
Mobile App -> Your Backend -> Third-Party API
user token secret API key
A more realistic flow looks like this:
[Mobile App]
| 1. User signs in
v
[Your Backend API]
| 2. Backend checks user/session
| 3. Backend reads API key from server env/secret store
v
[Third-Party API]
| 4. Response returns to backend
v
[Your Backend API]
| 5. Backend filters/logs/rate-limits
v
[Mobile App]
Why this matters architecturally
This backend layer is not just about hiding the key. It also gives you a place to:
- enforce per-user permissions
- apply rate limits (how many requests are allowed)
- log usage for billing or support
- transform responses into a mobile-friendly format
- swap providers later without forcing an app update
- revoke access quickly if something goes wrong
Without that layer, the third-party provider sees only your app key. It usually cannot tell which end user is making which request unless you build a more complex scheme on top.
How it actually works
Let’s walk one realistic example end to end.
Example: a mobile app that generates AI summaries
Suppose your app lets a signed-in user paste text and request a summary. The app needs to call a paid AI API that requires a secret key.
We do not put that key in the app. Instead:
- The user opens the app and signs in.
- The app gets a user session token from your backend. A token is a temporary proof of identity.
- The user taps Summarize.
- The app sends the text to your backend endpoint, for example
POST /api/summarize. - Your backend checks the user token.
- Your backend checks business rules, for example: is this user allowed to summarize, and are they under the daily limit?
- Your backend reads the AI provider key from a server environment variable or secret manager.
- Your backend calls the AI provider over HTTPS (encrypted web traffic).
- The provider returns the summary.
- Your backend logs the request, removes any fields you do not want exposed, and sends the result back to the app.
At no point does the mobile app receive the provider key.
What goes wrong in the direct-to-provider version
If the app called the AI provider directly, a determined user could:
- decompile the app package (turn the app back into readable assets/code)
- inspect strings and config files
- run the app through a debugging proxy and watch requests
- patch the app to bypass UI restrictions
- reuse the key from a script or another app
Then the attacker is no longer limited by your app’s screens. They can use your paid API key however they want until you rotate it (replace it with a new one).
Why "but we use HTTPS" does not solve it
HTTPS protects data in transit between the app and the server. It does not hide a secret from the app itself. If the app must send the key, the app must possess the key. That is the problem.
Why "but we’ll obfuscate it" does not solve it
Obfuscation only raises the effort slightly. It does not create secrecy. If the app can use the key, an attacker can usually recover it or replay the request.
When to use it (and when not to)
Here is the practical decision rule: if a credential grants access to paid usage, private data, write actions, or privileged operations, keep it on the server.
| Scenario | Recommendation |
|---|---|
| Mobile app calling a paid third-party API with one shared secret key | Use your backend as a proxy; do not ship the key in the app |
| Mobile app accessing your own backend with user login | Normal and recommended; app holds only user/session tokens |
| Public, read-only data with no secret and no billing risk | Direct calls can be acceptable |
| Service supports short-lived, scoped tokens minted by your backend | Good option; app gets temporary limited tokens, not the master key |
| SDK requires a "publishable" or "public" key explicitly meant for client apps | Usually acceptable, but only if the vendor documents it as non-secret |
| Internal admin app used by staff only | Still do not embed master secrets; staff devices are also untrusted |
You probably don’t need a backend proxy if...
- the API is truly public and anonymous
- there is no secret involved
- abuse would not create cost, data exposure, or account takeover risk
- the provider gives you a client-safe public key and keeps sensitive operations server-side
You definitely do need one if...
- the key can spend money
- the key can read or write customer data
- the key has broad account permissions
- you need per-user authorization, quotas, or audit logs
- you may change providers later and want the app contract to stay stable
Trade-offs
There is no magic here: the safer design costs more than putting a string in an app. We still recommend it because the downside of key leakage is usually worse.
| Benefit | What it costs |
|---|---|
| Secret key stays off user devices | You must run and monitor a backend service |
| You can enforce per-user permissions and limits | More application logic to build and test |
| Easier key rotation and emergency revocation | Operational process for secret management |
| Better logging and billing visibility | Storage and privacy considerations for logs |
| You can normalize or cache provider responses | Extra latency from one more network hop |
| Easier provider switching later | Initial architecture is more complex |
The honest operational costs
- Complexity: one more service, endpoint, and deployment pipeline.
- Money: backend hosting, logs, and possibly caching or queueing.
- Latency: app -> backend -> provider is slower than app -> provider, though often only by a small amount if hosted well.
- On-call burden: if the proxy is down, the feature is down.
- Security responsibility: you now must secure your backend properly, including rate limits, auth checks, and secret storage.
That said, the alternative is usually not "free and safe." It is "simpler today, expensive incident later."
In practice
Below are two concrete patterns you can adapt.
Example 1: Backend endpoint that calls the provider
This Express example accepts a user request, reads the provider key from the server environment, and forwards the call safely.
import express from "express";
const app = express();
app.use(express.json({ limit: "200kb" }));
app.post("/api/summarize", async (req, res) => {
const userId = req.header("X-User-Id");
if (!userId) {
return res.status(401).json({ error: "Unauthorized" });
}
const apiKey = process.env.AI_PROVIDER_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: "Server not configured" });
}
const { text } = req.body;
if (!text || text.length > 10000) {
return res.status(400).json({ error: "Invalid text" });
}
const upstream = await fetch("https://api.example-ai.com/v1/summaries", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
body: JSON.stringify({ input: text })
});
if (!upstream.ok) {
const detail = await upstream.text();
return res.status(502).json({ error: "Provider error", detail });
}
const data = await upstream.json();
return res.json({ summary: data.summary });
});
app.listen(3000, () => {
console.log("API listening on :3000");
});
What it does: the mobile app calls your endpoint, not the provider directly. The gotcha: do not trust X-User-Id in a real app by itself; replace it with real session or token validation.
Example 2: Store the secret in server environment, not in app code
In your hosting provider’s dashboard, open your app/service settings and look for Environment Variables, Secrets, or Configuration. Add a key named AI_PROVIDER_API_KEY with the provider value, then redeploy the service.
CLI version if your platform supports standard shell-style env vars:
export AI_PROVIDER_API_KEY="replace-with-real-secret"
node server.js
What it does: keeps the secret on the server host instead of in source control or the mobile app. The gotcha: environment variables are better than hard-coding, but a dedicated secret manager is stronger for larger systems because it improves rotation and access control.
Example 3: Mobile app calls your backend, not the provider
This curl command shows the exact request shape your app should make.
curl -X POST "https://api.yourcompany.com/api/summarize" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-d '{"text":"Summarize this customer note into three bullet points."}'
What it does: sends the user’s request to your backend using a user token, not the provider secret. The gotcha: the app should never log full tokens or sensitive text to device logs in production builds.
⚠️ If you have already shipped a mobile app with a real secret embedded, treat that key as exposed. Before changing code, rotate the key in the provider dashboard, then update the backend to use the new key. Rotating first can briefly break the feature until the backend is deployed, but leaving the old key active leaves you open to ongoing abuse.
If you already have a key in the app: the practical recovery plan
- In the third-party provider dashboard, find the API credentials page.
- Create a new key if the provider allows parallel keys.
- Put the new key in your backend secret storage.
- Deploy the backend.
- Change the mobile app to call your backend endpoint instead of the provider.
- Release the app update.
- Revoke the old key in the provider dashboard.
- Review provider usage logs for abuse during the exposure window.
If the provider does not allow parallel keys, schedule a short maintenance window because there may be a brief interruption while you swap credentials.
Further reading
- OWASP Mobile Application Security Testing Guide
- OWASP Mobile Top 10
- The "Authentication" and "Authorization" sections of the MDN Web Docs HTTP guides
- The "Security Best Practices" section of your cloud provider’s secrets manager documentation
- RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI