Contents Lesson 3 of 16

7 min read · foundations

Why your API key must never reach the browser

This is the lesson that decides whether your project is safe, and it comes third because the mistake it prevents is the one people make in their first hour.

The shape of the mistake

The EODHD API authenticates with a token in the URL:

https://eodhd.com/api/eod/AAPL.US?fmt=json&api_token=YOUR_TOKEN

The obvious thing to do in a web app is to call that from the page. It works instantly, which is the problem. Anything the browser fetches, the person using the browser can read — and so can any extension they have installed, any proxy in the way, and anyone who opens developer tools. If you ship a build with the key in it, the key is not "a bit exposed". It is published.

Public keys get found. Not by a person reading your source: by automated scanners that crawl deployments and public repositories continuously and cost nothing to run. The gap between "I deployed it" and "someone else is using my quota" is measured in days.

A leaked key on a paid plan is somebody else spending your money. A leaked key on a free plan is your tool silently breaking, because your daily allowance is gone by the time you wake up.

The fix: a server between you and the API

Put a small piece of your own server in the middle. The browser asks your server; your server adds the key and asks EODHD; the answer comes back. The key exists only in the server's environment and never travels to the client.

browser  →  /api/proxy/eod/AAPL.US        (no key anywhere)
                  ↓  your server adds api_token
            https://eodhd.com/api/eod/AAPL.US?api_token=...

That is the whole idea, and it is about thirty lines. In the starter repository it lives in two files:

  • src/lib/eodhd.ts — the only place in the codebase that reads the key.
  • src/app/api/proxy/[...path]/route.ts — the route the browser is allowed to call.

Two details in that proxy are worth more than the rest of it put together.

It drops any token the caller supplies. If someone calls your proxy with ?api_token=something, that parameter is thrown away before the request goes upstream. Without this, your proxy becomes a free relay for other people's keys — and, worse, a way to make requests that look like they came from you.

It is the only route out. No component fetches EODHD directly. The moment one does, the rule has a hole in it, and holes in this rule do not announce themselves.

Where the key actually lives

Locally, in .env.local, which is in .gitignore:

EODHD_API_KEY=your_key_here

In production, in your host's environment settings — on Vercel, the project's environment variables.

One naming rule that has burned many people: in Next.js, any variable prefixed NEXT_PUBLIC_ is deliberately inlined into the browser bundle. That prefix is a feature, and it is a loaded gun pointed at exactly this. EODHD_API_KEY has no prefix, and it must never get one.

Do not trust yourself — check the build

Rules are only as good as the check that enforces them. After a production build, search the output for your key's actual value:

npm run build
export $(grep -E '^EODHD_API_KEY=' .env.local)   # the shell does not read .env.local
grep -rF "$EODHD_API_KEY" .next/ && echo "LEAK" || echo "clean"

The export line is the part people drop, and dropping it inverts the check: Next.js reads .env.local, your shell does not, so an unset $EODHD_API_KEY makes grep search for the empty string, match every line in the build, and print LEAK on a perfectly clean one.

On the starter repository, built with a real key in the environment on 2026-08-25, that search finds nothing — not in the server bundle, not in a single client chunk. Run it yourself after any change to how data is fetched. It takes two seconds and it is the only form of this promise that is actually verified rather than believed.

The starter also carries a pre-commit hook that refuses a commit containing something key-shaped. It is a seatbelt, not a lock: it cannot see a key you never staged, and it cannot see one already in your history. If a key does reach a public place, rotate it. Deleting the commit is not enough — it was public, and the scanners are faster than you.

What npm install just did

You ran npm install a moment ago. package.json lists the packages you chose; the command fetches those and everything they depend on, pins the exact versions in package-lock.json, and unpacks them into node_modules/. Commit the lockfile so every machine installs the same thing; never node_modules.

The starter declares 11 packages; the lockfile resolves 434, all running with your permissions. Course 5 audits them.

One more thing that is not about secrets

The data you pull is licensed to you, not through you. EODHD's own wording is that the plans on the pricing page are "intended for personal use only as commercial use requires a more thorough approach to licensing and data use". Running this tool for yourself is personal use. Putting it on a public URL where other people read the numbers is a different conversation, and the licence page in this lesson's sources is where it starts.

Try it now

Clone the starter, put your key in .env.local, and run the grep above against a production build. Then break it on purpose: rename the variable to NEXT_PUBLIC_EODHD_API_KEY, use it in a client component, rebuild, and grep again. Seeing your own key sitting in a .next/static chunk is a thirty-second exercise you will remember for years. Change it back afterwards.