
How to Securely Store API Keys and Secrets in Frontend and Mobile Apps
When building web or mobile applications, you often integrate third-party APIs (such as Stripe for payments, OpenAI for AI models, or SendGrid for emails). To authenticate these requests, you are provided with API keys and secrets.
The most common developer security mistake is embedding these API keys directly inside frontend code or packing them inside client-side environment variables.
In this guide, we will outline why client-side environments can never securely store secrets, and explore the standard architecture patterns to protect your API keys.
The Truth: Client-Side Code Has No Secrets
Many developers believe that using tools like .env files in React or Vite, or running JS code obfuscation, hides their API keys from public view.
This is false. Any code that is compiled, shipped, and executed on a user's browser or mobile device can be inspected. There are two simple ways an attacker can steal keys from client code:
- Source Inspection: Obfuscation only scrambles variable names; it does not encrypt string values. Attackers can open the developer console, inspect the bundle files, and search for patterns matching API key strings.
- Network Sniffing: Even if the key is hidden in the source code, your client-side application must send it in the headers of request calls. Attackers can open the DevTools Network tab or use proxy sniffers (like Charles or Fiddler) to view the API key in the request logs.
If you embed your OpenAI API key directly inside a React app, an attacker can extract it in seconds and drain your billing account.
Solution 1: Implement a Backend Proxy (The Gateway Pattern)
The standard secure architecture is utilizing a backend proxy server or Serverless Function (like Next.js Route Handlers or Cloudflare Workers) to act as a bridge.
Instead of your frontend client calling the third-party API directly:
- The client sends a request to your local proxy server (authenticated using standard user session cookies).
- Your server validates the user's session.
- The server retrieves the API key from its secure server-side environment variables.
- The server executes the third-party API request and returns the filtered response back to the client.
Next.js Route Handler Proxy Example
// app/api/generate-text/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
// 1. Verify user session cookies here...
const { prompt } = await request.json();
// 2. Retrieve secure server-side API key
const apiKey = process.env.OPENAI_API_KEY;
// 3. Make server-to-server request
const res = await fetch('https://api.openai.com/v1/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: 'text-davinci-003',
prompt,
}),
});
const data = await res.json();
return NextResponse.json(data);
}The client simply fetches /api/generate-text. The actual API key is never exposed to the browser.
Solution 2: Token Exchange (Temporary Credentials)
If you must upload files directly to cloud storage (like Amazon S3 or Google Cloud Storage) from the client to prevent overloading your server's bandwidth, do not embed AWS root keys.
Instead, implement a Token Exchange Pattern:
- The client requests permission from your server to upload a file.
- The server uses its secure credentials to query AWS STS (Security Token Service) or Supabase Storage, requesting a Presigned URL or temporary AWS credentials with limited write permissions valid for only a short window (e.g., 15 minutes).
- The server sends this temporary URL or token back to the client.
- The client uploads the file directly to the cloud storage bucket using the temporary credentials.
Solution 3: Restrict API Keys by Origin
Some client-side APIs (like Google Maps or Firebase client SDKs) require public API keys in your HTML. These services are designed with public visibility in mind.
To secure these public keys, you must configure strict restrictions inside the provider's admin console:
- HTTP Referrer Restrictions: Bind the API key strictly to your domain (e.g.,
https://example.com/*). Even if someone steals the key, the provider will reject requests originating from other websites or localhost. - IP Restrictions: If the API key is used by server scripts, restrict its usage to your server's static IP address.
- API Restrictions: Configure the key to only be authorized to call specific APIs (e.g., a maps key cannot be used to query user databases).
Conclusion
Frontend code can never hold secrets securely. Any key embedded in JavaScript or mobile bundles can be extracted. To secure your integrations, always route third-party API calls through a secure backend proxy server, utilize presigned URLs for client uploads, and restrict all public client-side keys by domain origin to protect your billing and database resources.