How to hide an API key in frontend JavaScript
Published · 2 min read
You can’t. Anything your JavaScript can read, every visitor can read too. The fix is to keep the key on a server and have the browser ask that server to make the call.
This guide explains why the usual tricks do not work, then compares the three ways to keep a key on a server, with what each one costs you.
Why the key is always visible
The browser has to send the key to the API. To send it, it has to have it. Anyone can open the Network tab in their browser’s developer tools, click the request and read the Authorization header.
That is why these do not help:
- Environment variables in a frontend build. A bundler copies the value into the JavaScript file it ships. It is in the file, in plain text.
- Obfuscating or encoding the key. The request still carries the real key, so the Network tab still shows it.
- Splitting the key across files. Same request, same header.
What works: keep the key on a server
The browser calls a server you control. The server adds the key and calls the API. The browser only ever sees the answer.
| Option | Good for | What it costs you |
|---|---|---|
| A route in your own backend | An app that already has a server. | Nothing new to run. You write and secure the route. |
| A serverless function | A static site with one or two API calls. | A second deploy target, and you must stop strangers calling it. |
| A hosted tool that holds the key | Private dashboards and internal pages. | You trust a third party with the key. Not for public sites. |
A serverless function, in full
This one forwards a single, fixed request. It does not accept a URL from the browser, which is what stops it being used to call anything else.
// api/weather.js
export default async function handler(request) {
const city = new URL(request.url).searchParams.get('city') ?? 'Lisbon';
const upstream = await fetch(
'https://api.example.com/v1/weather?city=' + encodeURIComponent(city),
{ headers: { authorization: 'Bearer ' + process.env.WEATHER_API_KEY } }
);
return new Response(await upstream.text(), {
status: upstream.status,
headers: { 'content-type': 'application/json' },
});
}
Restrict the key anyway
A key on a server can still leak, through a log line or a commit. Give it the least it needs: read-only scopes, one project, a spending cap, and an allowed IP range if the provider offers one. Rotate it when someone leaves.