ContentForger

Guides

API Keys Security: How to Keep Secrets Out of Your Code

API keys security for first-time app builders: where secrets belong, how env variables and NEXT_PUBLIC_ work, and what to do after a leak.

By Thomas Lovas Lokoy — 2026-10-17, last updated 2026-10-17

A brass padlock with a key in it
Photo: Padlock Key by yawning hunter · CC BY 2.0 · cropped
On this page (10)
  1. What counts as a secret
  2. Why API keys in code are a problem
  3. Where secrets should live instead
  4. The NEXT_PUBLIC_ trap
  5. Keep secrets on the server side of your own code
  6. Limit the damage before anything goes wrong
  7. Catch mistakes automatically
  8. What to do when a key leaks
  9. A five-minute check for your project
  10. Sources

An API key is a password that a program uses. Anyone who holds it can act as your app: send email from your account, read your database, or run up a bill with a paid AI or SMS provider. API keys security for a first web app comes down to one rule that is easy to state and easy to break: the secret lives in the environment where the code runs, never in the code itself. This guide covers what counts as a secret, where it should live, how a Next.js project keeps it on the server, the tools that catch mistakes, and what to do on the day a key leaks anyway.

What counts as a secret

OWASP's Secrets Management Cheat Sheet gives API keys, database credentials, IAM permissions, SSH keys and certificates as examples. For a typical first web app the list usually looks like this:

  • Payment provider secret keys
  • Database connection strings and service-role keys
  • API keys for email, SMS, maps or AI services
  • Webhook and session signing secrets
  • OAuth client secrets

Some values look like keys but are designed to be public, such as an analytics ID or a "publishable" key meant for the browser. The test: if a stranger who copied the value could spend your money, read private data or impersonate your server, it is a secret.

Why API keys in code are a problem

Code gets copied: to GitHub, to a collaborator's laptop, into a zip file for a client, into the prompt history of an AI assistant. Every copy of the code is a copy of the key.

The OWASP Top 10:2025 treats this as a recognised weakness, not an edge case. Its Security Misconfiguration entry recommends identity federation, short-lived credentials or platform role-based access "instead of embedding static keys or secrets in code, configuration files, or pipelines", and its Cryptographic Failures entry asks, as one of its warning signs, whether crypto keys are checked into source code repositories. For the wider picture, see The OWASP Top 10 Explained for First-Time App Builders.

Git makes it stickier: deleting the line in a later commit does not remove it from history. GitHub's documentation on secret scanning reflects that:

Secret scanning scans your entire Git history on all branches of your repository for hardcoded credentials, including API keys, passwords, tokens, and other known secret types.

Where secrets should live instead

On your machine: an env file that git ignores

Next.js has built-in support for loading environment variables from .env files into process.env, so server code can read a value by name instead of containing it. The Next.js documentation is blunt about those files: "You almost never want to commit these files to your repository." It also notes that the default create-next-app template adds the env files to .gitignore for you.

A pattern that works for almost every project:

  • Put real values in .env.local, which stays on your machine.
  • Commit a .env.example that lists every variable name with an empty value, so the next person knows what to fill in.
  • Open .gitignore and confirm the env files are listed before your first commit, not after.

Two details catch people out. First, .gitignore only affects files git is not already tracking; GitHub's documentation says that for a file already checked in, "you must untrack the file before you add a rule to ignore it", using git rm --cached. Second, Next.js checks several files in a fixed order (the real environment first, then .env.development.local or its production equivalent, then .env.local, then the mode-specific file, then .env) and stops at the first match. If a value seems to be ignored, a file higher in that order is usually overriding it. In the test environment, .env.local is not loaded at all.

In production: your host's environment settings

Your hosting provider has a settings page for environment variables. Use the names from .env.example and paste production values there. The secret never passes through your repository.

The NEXT_PUBLIC_ trap

This is a common way for a Next.js app to leak a key without any git mistake at all.

By default, Next.js environment variables are only available on the server. Prefixing a variable with NEXT_PUBLIC_ changes that: Next.js inlines the value into the JavaScript bundle at build time, and in the documentation's words, "It will be inlined into any JavaScript sent to the browser." Anyone can open the browser's developer tools and read it.

Variable nameWhere it can be readSafe for secrets?
STRIPE_SECRET_KEYServer code onlyYes
NEXT_PUBLIC_ANALYTICS_IDServer and every visitor's browserNo
NEXT_PUBLIC_STRIPE_SECRET_KEYEvery visitor's browserNever do this

If a tutorial or code generator suggests adding the prefix to make an error go away, the error is telling you that browser code is trying to use a secret. Move that call to the server instead.

Keep secrets on the server side of your own code

Unprefixed variables can still leak if server code hands them to the browser. The Next.js data security guide draws the line clearly: Server Components can safely access environment variables and secrets, while Client Components "must follow the same security assumptions as code running in the browser."

Three habits help:

  • One place reads secrets. The same guide recommends that only a server-side data access layer touches process.env, which keeps secrets from spreading through the rest of the app.
  • Mark server modules. Adding the server-only import to a module causes a build error if a client component imports it, so a mistake fails loudly on your machine instead of silently in production.
  • Return only what the UI needs. Server Action return values are serialised and sent to the client. Return a success flag or a trimmed object, never a raw record or a configuration object that happens to contain a token.

When the browser needs a paid third-party API, call your own Route Handler and let it attach the key. The key stays on the server, and you get a natural place for validation and rate limiting.

Limit the damage before anything goes wrong

Assume a key will leak eventually and keep the damage small:

  • Use separate keys per environment. OWASP recommends configuring development, QA and production the same way but with different credentials, so a key from a laptop cannot touch production data.
  • Give each key the least access it needs. The OWASP cheat sheet recommends scoping access per secret and per service. Use restricted keys where a provider offers them.
  • Rotate on a schedule. In OWASP's words: "You should regularly rotate secrets so that any stolen credentials will only work for a short time." The Next.js backend guide says the same: rotate credentials and API keys regularly.
  • Set spending caps. OWASP's API Security Top 10 recommends spending limits with third-party providers and cloud services, or billing alerts where limits are not available.
  • Keep secrets out of logs. The cheat sheet warns that environment variables "are generally accessible to all processes and may be included in logs or system dumps." Never print the environment or a full request with its headers to your logs.

Catch mistakes automatically

  • Push protection. GitHub describes it as follows: "Rather than alerting you to credential leaks after the fact, push protection blocks pushes that contain secrets before they reach your repository." Push protection for users is on by default on GitHub.com and stops you pushing secrets to public repositories.
  • Secret scanning. On public repositories it is free and runs automatically, raising an alert when a known credential format appears anywhere in history. Private repositories need GitHub Secret Protection on a paid plan.
  • Checks on your own machine. OWASP suggests enabling secret detection at the developer level, for example with a pre-commit hook, and using more than one tool because no single tool catches everything.

What to do when a key leaks

Order matters:

  • Revoke or rotate the key first. GitHub's guidance is that "as a first step you need to revoke and/or rotate that secret." Once the old key is dead, the copy in history is harmless. Restarting your app does not revoke anything; revocation happens at the provider.
  • Put the new key in your host's settings and redeploy.
  • Check the provider's usage and billing pages for activity you do not recognise.
  • Clean up history only if you still need to. GitHub documents the git-filter-repo tool for removing a file or string from every commit, and lists the side effects: rewritten commit hashes, lost pull request diffs, and the risk that a collaborator's old clone puts the data back. As the documentation puts it, "It is unfortunately easy to re-push the sensitive data to the repository and make a bigger mess."
  • Remove the secret from anywhere else it landed, such as logs or chat messages.

A five-minute check for your project

  • Search the repository for the first characters of each real key.
  • Confirm .env.local is listed in .gitignore and does not appear in your commit history.
  • Check that no NEXT_PUBLIC_ variable holds a secret.
  • Confirm production values live only in your host's settings.
  • Turn on push protection and secret scanning where your plan allows.

Secrets are one item on a longer list; the pre-launch checklist for generated code covers the rest.

Sources

ShareXLinkedInFacebook

Was this article helpful?

New guides in your inbox

One short email when we publish something useful — tutorials, comparisons and launch checklists. Unsubscribe in one click.

Related articles