Cybersecurity / Networking

API Key Exposed in Frontend Code, How to Fix

An API key exposed in frontend code is a genuine security incident, not just a code smell — anything shipped to the browser is visible to anyone who opens developer tools, view-source, or inspects network requests, regardless of how the key is obfuscated or minified.

The Problem

An API key, secret, or credential was included directly in frontend JavaScript, either hardcoded or pulled from an environment variable that got bundled into the client-side build:

// This is visible to anyone who views your site's JavaScript
const API_KEY = "sk_live_abc123...";

Once shipped to production, this key is effectively public, regardless of whether anyone has actually found and misused it yet.

Why It Happens

This typically happens through one of a few common paths:

  • A developer directly hardcodes a key while building a feature quickly, intending to move it server-side later, and that step gets missed before deployment
  • An environment variable intended for server-side use gets accidentally exposed to the client-side bundle — common in frameworks that require a specific prefix (like NEXT_PUBLIC_ or VITE_) to distinguish client-exposed variables from server-only ones, where a naming mistake exposes something that should have stayed private
  • A third-party service's documentation shows a "quick start" example using the key directly in frontend code, and that example gets used as-is in production rather than being adapted to a proper backend proxy pattern
  • A key with genuinely limited, safe-to-expose permissions is confused with a different, more powerful key for the same service, and the wrong one ends up in frontend code

The Fix

First, immediately rotate the exposed key at the provider — this is not optional and shouldn't wait until the architectural fix is complete, since the key should be treated as compromised the moment it was deployed to production, regardless of whether misuse has been observed yet:

# Specific steps vary by provider, but generally:
# 1. Generate a new key in the provider's dashboard
# 2. Update your server-side configuration with the new key
# 3. Revoke/delete the old, exposed key

Check the provider's usage logs for any unexpected activity during the window the key was exposed, since rotating the key prevents future misuse but doesn't undo anything that may have already happened.

For the actual architectural fix, move any operation requiring a sensitive key to a backend endpoint that the frontend calls instead of using the key directly:

// Frontend: calls your own backend, never touches the real key
fetch('/api/proxy/search', { method: 'POST', body: JSON.stringify({ query }) });

// Backend: holds the actual key, makes the real request server-side
app.post('/api/proxy/search', async (req, res) => {
  const result = await externalApi.search(req.body.query, {
    apiKey: process.env.EXTERNAL_API_KEY, // never sent to the client
  });
  res.json(result);
});

This pattern ensures the actual sensitive key never leaves your server, regardless of how the frontend code is inspected.

If your framework distinguishes between client-exposed and server-only environment variables, double-check every environment variable's naming convention carefully, since a single mismatched prefix can expose something that was intended to stay private:

# .env - only variables explicitly prefixed are exposed to the client
NEXT_PUBLIC_ANALYTICS_ID=safe-to-expose-value
API_SECRET_KEY=must-never-be-prefixed-with-NEXT_PUBLIC

For keys that genuinely need to exist client-side (some services are specifically designed to have a "public" key safe for frontend use, distinct from a private server-side key), confirm you're using the correct, intentionally public variant of the key, not the private one, and that the provider's own permission scoping limits what that public key can actually do even if misused.

Still Not Working?

If you're unsure whether other secrets might also be exposed somewhere in your frontend bundle, audit the built output directly rather than only reviewing source code, since a secret could enter the bundle through a dependency or build configuration issue that isn't obvious from reading your own application code alone:

grep -r "sk_live\|api_key\|secret" dist/ build/

Running a search like this across your actual production build output, using patterns specific to your provider's key formats, can surface exposures that code review alone might miss, especially in larger projects with many dependencies and build steps.