DEV Community

Cover image for How to Secure API Keys in Chrome Extension?
Himanshu
Himanshu

Posted on

How to Secure API Keys in Chrome Extension?

Here’s how i handle my API keys, like OpenAI keys, Gemini keys, Stripe Keys in my chrome extension

 

Why You Can't Put API Keys in Client-Side Code?

Let's get this out of the way first: never, ever put your API keys directly in your Chrome extension's client-side code. Here's why:

The Core Problem

When someone installs your Chrome extension from the Web Store, they're downloading the exact same files you uploaded. Yes, that means:

  • All your JavaScript/TypeScript code
  • All your configuration files
  • And yes, any API keys you've included
  • here’s the entire source code of Grammarly (hahahaha)

.env file will protect me? Not really.

When you build your extension, those environment variables get bundled into your code. Anyone can:

  1. Right-click your extension
  2. Click "Manage Extensions"
  3. Click "Details"
  4. Find the extension's folder on their computer
  5. Open your bundled JavaScript files
  6. Search for your API key

It takes less than 30 seconds.

What Happens When Keys Are Exposed?

Once your API key is compromised:

  • OpenAI/Gemini: Someone could rack up thousands of dollars in API costs on your account
  • Mailgun: Spammers could use your account to send malicious emails
  • Any paid API: Your credit card becomes their unlimited resource

What About WebAssembly (WASM)?

You might have heard about hiding keys in WebAssembly. While WASM does obfuscate your code, it's not a security solution:

  • WASM can still be decompiled with tools
  • It adds complexity to your development workflow
  • It's fragile and can break with browser updates
  • Chrome web store’s reviewing team will not like this

Don't waste your time with WASM for API key protection. There's a better way.

 

The Right Way: Use a Backend Proxy (It's Easier Than You Think)

I know you hate to hear this and i hate to say this but: API keys should only live on the server, never on the client side.

but relax… we have an easy option which is Serverless functions, when you hear the word “serverless” it’s not like an all-time running machine that you need to purchase and then install arch on it and then deploy it on IBM’s servers and pay monthly electricity bills and protect it from bitcoin minors….

no

serverless functions live and die quickly and you don't need to manage them, even though they also run on real servers but they are managed by cloud providers (like vercel, cloudflare, etc) and they are mostly free.

What actually is a Serverless Function?

a serverless function is just a javascript function that will run whenever you call it from your frontend application (chrome extension) using fetch() method (same as you call any api endpoint, an api endpoint is just an ugly looking url that your cloud provider will give you when you upload/deploy your javascript function there :)

you have soo many options and most of them have a free tier, like:

  1. vercel
  2. netlify
  3. supabase functions
  4. Appwrite
  5. cloudflare (pretty scary for beginners but the most cheapest one in this list)
  6. AWS Lambda (i can’t even find their pricing page, just kidding :)

see, these providers don’t only provide serverless functions, they have other billion things too, but you can still only deploy your functions and ignore rest of the fuss and they will give you an api endpoint (url) for free, don't even need to purchase a domain (though you can do it)

let’s take vercel as example,

first create a very minimal NextJs project and then

Step 2: Create Your Vercel Project

  1. Go to vercel.com and sign up
  2. Create a new project
  3. You can start from scratch or connect a GitHub repo

Step 2: Example of a Serverless Function

Create a file at /api/chat.ts in your project:

// app/api/chat.js

export default async function handler() {
    try {
    // Call OpenAI API with YOUR key
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` 👈
      },
      body: JSON.stringify({
        model: 'gpt-3.5-turbo',
        messages: [{ role: 'user', content: message }]
      })
    });

    const data = await response.json();
    return res.status(200).json(data);
  } 

  /// rest of the code
}

Enter fullscreen mode Exit fullscreen mode

Now you can add your keys in this project’s .env file or:

  1. Go to your project settings in Vercel
  2. Navigate to "Environment Variables"
  3. Add OPENAI_API_KEY with your actual API key
  4. Save it

Step 4: Call It from Your Extension

In your Chrome extension's popup/content script/background script:

// In your extension
async function getChatResponse(message) {

const API_ENDPOINT = 'https://your-project.vercel.app/api/chat' 👈 // vercel will give you this url when you deploy your project, for free

  const response = await fetch(API_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ message })
  });

  return await response.json();
}

Enter fullscreen mode Exit fullscreen mode

Supabase as database

now here’s the thing, if you are using supabase or firebase as your database they give you publishable api keys (meaning you can put these api keys on dark website, doesn't matter that much :)

  • It's meant to be public
  • Supabase has Row Level Security (RLS) that protects your data (you need to enable and write it manually btw)
  • All security happens on the server side through RLS policies

So in your Chrome extension, this is perfectly safe:

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-publishable-key' // This is safe to expose!
);

Enter fullscreen mode Exit fullscreen mode

that’s pretty much it,

that was a reeeeeally basic blog, but i wish somebody told me all these things when i was starting my extension journey so yeah,

btw if you don’t want to setup all the serverless functions, stripe payments, login/signup, etc, you can checkout my production-ready boilerplate, it’s called extFast

thankyou so much for reading 💛

bye bye :)

Top comments (0)