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.

OptionGood forWhat it costs you
A route in your own backendAn app that already has a server.Nothing new to run. You write and secure the route.
A serverless functionA 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 keyPrivate 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.

Keep reading

CORS error on fetch: what causes it and how to fix itWhy the browser blocks the call, and which fix fits which case.SSRF protection for a fetch endpoint in Node.jsPrivate ranges, DNS rebinding, redirects and IPv6, one at a time.SecretsStore an API key once, choose the hosts it may go to, bind it to a pane and use it with a placeholder. How values are redacted, rotated, deleted and stored.