Ever pushed code to GitHub and wondered if you just leaked something you shouldn’t have? You probably did. Studies show over 6 million secrets get exposed on GitHub every year, and API keys top the list. Once an API key hits your git history, it’s game over unless you scrub it out completely. The good news is environment variables and backend proxies can lock down your credentials in under 10 minutes. This guide walks you through setting up .env files, building simple proxy servers, and choosing the right secret management approach for your stack without the enterprise overhead.
Quick Start: Environment Variables and Git Security

Hardcoding API keys in your source code is asking for trouble. Anyone with access to your codebase can grab those credentials with a quick right-click and view source. And if you push API keys to GitHub? They’re stuck in your git history forever, even after you delete them. Plus, anyone with browser devtools can intercept API keys sent in network requests.
Environment variables solve this by keeping sensitive data completely separate from your code. You’ll need .env files to store credentials locally and .gitignore to block those files from version control. These two work together. Using just one leaves you exposed.
Here’s how to set it up:
-
Create a .env file in your project root. Format matters. Use
REACT_APP_API_KEY=your_api_key_valuefor React, orAPI_KEY=your_api_key_valuefor backend apps. No quotes. No spaces around the equals sign. -
Access variables in code through
process.env. In JavaScript that’sconst apiKey = process.env.REACT_APP_API_KEY. Python usesimport os; api_key = os.getenv('API_KEY'). -
Add .env to .gitignore immediately before you commit anything. Open (or create)
.gitignorein your project root and add.envon its own line. Do this before your first commit. -
Verify it worked by running
git statusafter adding files. Your .env file shouldn’t show up in tracked changes. -
Configure environment variables on your host manually. Vercel, Netlify, Heroku and similar platforms make you enter these in project settings dashboards, then redeploy.
-
Restart your app whenever you change environment variables. Your .env changes won’t take effect until you stop and restart your dev server or redeploy.
-
Test locally by starting your server (usually
localhost:3000) and confirming your app can read the variables without errors.
GitHub Actions and other CI/CD pipelines need separate setup. These systems have their own secret storage. GitHub Actions uses encrypted secrets in repo settings, GitLab CI has protected variables, Jenkins offers credential bindings. Don’t commit pipeline credentials to your deployment workflows.
If you already committed API keys to your repo, the damage sits in your git history unless you act fast. Here’s what to do:
- Rotate compromised credentials immediately. Generate new API keys from your provider and revoke the exposed ones. This comes first.
- Clean git history with tools like
git filter-branchorBFG Repo-Cleanerto remove sensitive data from all commits, then force push the cleaned history. - Run secrets scanning tools like GitGuardian, TruffleHog, or GitHub’s built in scanner to find all exposed secrets across your codebase.
- Audit your entire repository reviewing every file and commit for hardcoded credentials, config files with plain text secrets, and other exposed data.
- Update all deployment environments with new credentials after rotation. That includes dev, staging, and production configs.
- Set up monitoring for exposed secrets using automated scanning in your CI/CD pipeline to catch future commits with sensitive data before they hit your main branch.
Backend Proxy Architecture for Complete API Key Protection

Frontend environment variables aren’t enough because keys stay visible in bundled browser code. Anyone can pop open devtools, inspect your JavaScript files, and pull out API keys. Even if they started in a .env file, they end up in the final bundle that ships to users.
How Backend Proxy Architecture Works
The backend proxy pattern puts a middleman between your frontend and third party APIs. Your client code calls your own backend endpoint instead of hitting external APIs directly. The backend server grabs the API key from secure storage (environment variables or secret management systems), makes the authenticated request to the third party API, gets the response, and sends the data back to your frontend without ever exposing authentication tokens.
The request flow looks like this: User clicks button → Frontend calls yourserver.com/api/search?q=hobbit → Your backend reads process.env.API_KEY server side → Backend requests thirdpartyapi.com/search?key=secret&q=hobbit → Third party API returns data → Your backend strips any sensitive headers → Clean response goes to frontend.
This stops browser inspection dead because the API key never leaves your server. Network monitoring tools only see requests to your own backend, never the actual third party API credentials.
Implementation for Web Applications
For Node.js, install the packages you need: npm install express cors axios. Create a server endpoint that works as the proxy:
const express = require('express');
const axios = require('axios');
const app = express();
app.get('/api/search', async (req, res) => {
const apiKey = process.env.API_KEY;
const query = req.query.q;
const response = await axios.get(`https://thirdpartyapi.com/search`, {
params: { key: apiKey, q: query }
});
res.json(response.data);
});
Next.js makes this easier with built in solutions. Next.js API Routes let you create server side endpoints in the pages/api directory that keep API keys hidden while handling requests. For example, pages/api/search.js runs entirely server side, accessing process.env.API_KEY safely.
Next.js Server Actions let you call server side logic from client side while protecting sensitive info. They keep API keys on the server while enabling stream based communication with client components. Particularly useful for handling file upload progress without exposing credentials.
Backend Proxy for Mobile Applications
Mobile apps face extra risks because app binaries can be reverse engineered. iOS keychain and Android keystore protect credentials at rest, but determined attackers can decompile your app, extract embedded API keys, and use them without authorization.
Backend proxy architecture matters even more for mobile security because it keeps authentication tokens completely off the device. Your iOS or Android app calls your controlled backend server, which holds the actual API keys server side. Even if someone reverse engineers your mobile app binary, they’ll only find references to your proxy endpoints, not the underlying third party API credentials.
Backend proxy solutions add resource usage and latency to every request. Your server becomes a middleman handling traffic between clients and APIs. For simple read operations, this overhead might be fine. But for large file uploads, the challenge with API Routes involves difficulty handling file upload progress while data streams through your proxy.
When backend proxy makes sense: any time API keys would be exposed in client side code (web or mobile), when you need request logging and rate limiting, or when combining multiple API calls server side. Environment variables alone work when you have genuine server side code (Node.js backend, Python Flask, etc.) that never ships to browsers.
| Implementation Approach | Security Level | Complexity | Best For |
|---|---|---|---|
| Environment Variables Only | Low (client side) / High (server side) | Very Low | Backend services, build time configs, non browser environments |
| Next.js API Routes | High | Low | Simple proxy needs, read operations, projects already using Next.js |
| Next.js Server Actions | High | Medium | Real time updates, file uploads with progress, streaming data |
| Custom Express Proxy | High | Medium to High | Framework agnostic projects, custom middleware needs, multiple API integrations |
Understanding API Key Exposure Risks and Security Vulnerabilities

API keys work like secret passwords that prove your identity to API providers. When your code makes a request with a valid API key, the provider knows it’s you and grants access. Most API keys don’t expire. Once issued, they work forever unless you manually revoke them. This makes them particularly dangerous when exposed, because there’s no automatic timeout like session tokens have.
Attackers find exposed credentials through three main paths. First, source code inspection. Anyone can view your JavaScript files in browser devtools, search for “API” or “key,” and extract hardcoded credentials in seconds. Second, network interception. Opening the Network tab shows every request your app makes, including headers and query parameters where API keys often hide. Tools like Burp Suite or mitmproxy can intercept HTTPS traffic on a device the attacker controls. Third, repository commits. Public GitHub repos get constantly scraped by bots hunting for credentials, and even private repos expose keys to anyone with read access.
Exposed API keys let attackers make unauthorized requests that you get billed for. If someone finds your API key for a paid service, they can make unlimited requests until you hit budget limits. Potentially racking up thousands in charges. They gain unauthorized access to any data those API keys can reach: private info, customer data, internal systems. Data theft is just the start. Attackers can manipulate or delete your data, disrupt your service, or use your credentials to attack other systems. Financial liability and data breaches follow quickly from a single exposed authentication token.
Cloud Based Secret Management Systems for API Keys

Cloud based key management systems provide enterprise grade secret management with built in encryption, access control, and automated credential rotation. Unlike basic environment variables stored as plain text files on your server, these systems encrypt credentials both at rest and in transit. They offer fine grained access permissions, audit logs showing who accessed which secrets when, and API based retrieval that integrates into your deployment process.
AWS Secrets Manager, Azure Key Vault, and HashiCorp Vault are the three major providers. AWS Secrets Manager integrates tightly with other AWS services, supports automatic credential rotation for RDS databases and other AWS resources, and charges per secret stored plus API calls. Azure Key Vault works similarly within the Azure ecosystem, supporting hardware security modules (HSMs) for additional encryption protection. HashiCorp Vault is platform agnostic, runs on premises or in any cloud, and offers more flexibility for complex access policies and dynamic secret generation. At the cost of higher implementation complexity and operational overhead.
Enterprises should adopt key management systems when they need automated credential rotation, compliance with security standards requiring encryption and audit trails, or management of hundreds of credentials across multiple teams and environments. Integration typically involves installing a provider’s SDK, authenticating your app using IAM roles or service accounts, then retrieving secrets programmatically at runtime instead of reading from .env files. For smaller projects or teams, the complexity and cost of key management systems outweigh the benefits. Stick with environment variables and backend proxy servers until your security requirements justify the upgrade.
Deployment Configuration Across Platforms and CI/CD Pipelines

Different environments need different credentials. Your dev database shouldn’t use the same password as production, and test API keys should have rate limits preventing expensive mistakes. Environment specific credential management ensures leaked dev credentials don’t compromise production systems.
Local Development Workflow
Local development uses .env files stored on your machine and excluded from version control. Start your server on localhost:3000 or your preferred dev port, and your app reads credentials from process.env without touching production systems. When you change environment variables in your .env file, you must restart your dev server. Node.js, Python, and most runtimes load these values once at startup.
Real talk: developers often share a “development” API key in team documentation. That’s fine as long as it’s clearly labeled, has strict rate limits, and can be rotated without breaking production. Never use production credentials locally.
Framework Specific Deployments
React apps built with Create React App require environment variables to start with REACT_APP_ prefix. Access them via process.env.REACT_APP_API_KEY. But remember, these get bundled into your browser JavaScript during the build process. They’re only “environment variables” at build time, not runtime.
Next.js offers better options through Server Actions and API Routes for keeping credentials server side. Server components can access any environment variable directly. Client components cannot. Instead, they call API Routes or Server Actions that run on the server and handle authentication there.
Python projects use the python-dotenv package: from dotenv import load_dotenv; load_dotenv() at the top of your app, then os.getenv('API_KEY') to retrieve values. Java apps typically use properties files or system properties, accessed via System.getenv("API_KEY").
The build process matters. If your framework creates a static bundle (like a React SPA), any environment variables referenced in client side code will be baked into that bundle as plain text. If your framework does server side rendering or runs on Node.js, environment variables can stay server side.
Cloud Platform Configuration
Vercel, Netlify, and similar platforms require manual entry of environment variables in project settings dashboards. Log into your project, navigate to Settings → Environment Variables, add each key value pair, specify which environments they apply to (production, preview, development), then trigger a redeployment. Changes don’t take effect until you redeploy.
Lambda environment variables in AWS get configured per function through the Lambda console or infrastructure as code. Your function code reads them via process.env.API_KEY exactly like local development. Azure Functions and Google Cloud Functions work similarly. Configure variables in the function settings, access them in code via standard environment methods.
Serverless functions introduce a gotcha: cold starts. Your function might initialize and read environment variables dozens of times per minute during traffic spikes. Some developers cache secrets after the first read to avoid repeated overhead.
Container and Orchestration Security
Docker secrets and Kubernetes secrets handle sensitive data in containerized apps. Docker secrets integrate with Docker Swarm, mounting secrets as files inside containers instead of environment variables. Read them from /run/secrets/secret_name.
Kubernetes secrets are base64 encoded by default (not encrypted), stored in etcd, and injected into pods either as environment variables or mounted files. For production deployments, enable encryption at rest in your Kubernetes cluster config and use role based access control (RBAC) to restrict which pods can access which secrets.
The container approach differs from traditional environment variables because secrets can be updated and rotated without rebuilding images. You update the secret in Kubernetes, restart the pods, and new containers mount the updated values.
CI/CD and Infrastructure as Code
GitHub Actions stores secrets in repo settings under Settings → Secrets and variables → Actions. Reference them in workflow files as ${{ secrets.API_KEY }}. These secrets are encrypted and never appear in logs. GitLab CI and Jenkins offer similar functionality. Define secrets in project or pipeline settings, reference them in your build process and continuous integration scripts.
Configuration injection timing matters. Some teams inject secrets during the build process (environment variables available to build scripts), others inject at deployment (secrets only available to running apps). Build time injection risks baking secrets into artifacts. Runtime injection keeps credentials separate but requires your infrastructure to provide them.
Terraform variables and Ansible vault enable infrastructure as code automated deployment while protecting credentials. Terraform supports sensitive variables that won’t appear in plan output or state files if configured correctly. Ansible vault encrypts entire files or specific variables, requiring a vault password to decrypt during playbook execution.
Best practices for environment separation:
- Never share credentials across environments. Production, staging, and dev should use completely separate API keys, even for the same service.
- Rotate secrets automatically in production. Use your cloud provider’s automated deployment workflows to rotate database passwords, API keys, and certificates every 90 days.
- Inject config at the last possible moment. Secrets should enter your app at runtime in production, not during build process.
- Test validation before production deployment. Run integration tests in staging with staging credentials to catch config errors before they hit production.
- Enable audit logging for credential access. Track which services and people accessed which secrets when, storing logs in a separate system.
- Use environment specific naming conventions. Prefix secrets with environment indicators like
PROD_API_KEYandDEV_API_KEYto prevent accidental mixing.
Advanced API Key Security Techniques and Access Control

Defense in depth strategy means combining multiple protection methods so one failure doesn’t expose everything. Start with environment variables and .gitignore to keep secrets out of code repos. Add backend proxy servers for extra protection when your frontend needs API access. Use key management tools for advanced encryption needs in regulated industries. Layer on IP address restriction as an additional security measure. Configure your API provider to only accept requests from your server’s IP addresses, blocking stolen keys used from other locations.
IAM roles and service accounts follow the principle of least privilege. Grant each component only the permissions it actually needs. Instead of one master API key with full access, create separate service accounts for different functions. Your data processing job gets read only database access. Your user facing API gets read write to specific tables. Your backup script gets write only to storage. If one component is compromised, the blast radius stays limited.
OAuth tokens and JSON Web Tokens (JWT) provide alternatives to traditional API keys with better security properties. OAuth issues short lived access tokens that expire automatically. No zero expiration date problem. JWT authentication embeds claims and expiration times in the token itself, enabling stateless verification. Bearer tokens transmitted in HTTP Authorization headers are easier to secure than API keys passed as query parameters. The tradeoff is complexity. OAuth requires an authorization server and token refresh logic.
Monitoring tools and audit logs detect security vulnerabilities after they occur. Configure your API providers to log all requests with timestamps and source IPs. Feed those logs into monitoring tools that alert on suspicious patterns: requests from unexpected geographic locations, sudden traffic spikes, access to unusual endpoints. Review existing codebases quarterly to identify hardcoded API keys requiring migration to secure storage methods. Automated scanning tools help, but manual code review catches edge cases like keys in comments, test files, and config examples.
Final Words
Environment variables paired with .gitignore provide the baseline for how to hide API keys in code, but production systems need layered defenses.
Backend proxy architecture keeps credentials completely off the client side. Cloud secret managers add encryption and rotation for teams handling sensitive data at scale.
The right approach depends on your stack and threat model. A side project might be fine with .env files. An enterprise app handling user data needs the full stack: secret managers, IAM roles, and audit logging.
Start with the quick fixes. Rotate any exposed keys. Add monitoring. Your future self (and your security team) will thank you.
FAQ
How do you keep API keys hidden in your applications?
You keep API keys hidden by storing them in environment variables using .env files and adding those files to .gitignore before committing code. For client-side applications, implement a backend proxy that makes authenticated API requests server-side, preventing keys from appearing in browser-inspectable code.
How do you hide API keys in Python code?
You hide API keys in Python code by storing them in a .env file using the python-dotenv package and accessing them via os.getenv() or load_dotenv() methods. Never hardcode keys directly in Python files, and always add .env to your .gitignore before committing to version control.
How do you hide the API key in VS Code projects?
You hide API keys in VS Code projects by creating a .env file in your project root, adding it to .gitignore immediately, and accessing values through environment variable loaders. VS Code won’t commit .gitignore-listed files when properly configured, protecting credentials from repository exposure.
How do you hide API keys when sending them in request headers?
You hide API keys in headers by handling authentication server-side through backend proxy architecture. Your frontend calls your backend endpoint, which adds the API key to headers before forwarding requests to third-party APIs, keeping credentials invisible to browser devtools and network inspection.
What security risks happen when API keys are exposed?
API key exposure creates unauthorized access to your services, potential financial liability from exceeded usage quotas, and risks of data theft or manipulation. Exposed credentials enable attackers to impersonate your application, access private data, and potentially delete or corrupt information without your knowledge.
When should you use environment variables versus backend proxy for API security?
You should use environment variables for server-side code and scheduled jobs, while backend proxy architecture is essential for any client-facing application where code runs in browsers or mobile apps. Backend proxies prevent reverse engineering and browser inspection, which environment variables alone cannot protect against.
What’s the difference between .env files and cloud secret management systems?
.env files provide basic credential separation for development and simple deployments, while cloud secret management systems like AWS Secrets Manager offer enterprise features including encryption, automated credential rotation, access control policies, and audit logging. Teams needing advanced encryption should adopt key management systems.
How do you fix API keys already committed to Git history?
You fix committed API keys by immediately rotating the exposed credentials, using git history rewriting tools to remove secrets from repository history, running secrets scanning tools, conducting a security audit, updating all deployment environments, and implementing monitoring for unauthorized usage of the exposed keys.
How do you configure environment variables in CI/CD pipelines?
You configure environment variables in CI/CD pipelines using platform-specific secrets management like GitHub Actions secrets, GitLab CI variables, or Jenkins credentials. These systems inject secrets during the build process without storing them in repository files, enabling automated deployment while maintaining security.
What are the best practices for separating API keys across environments?
The best practices for separating API keys include never sharing credentials between development, staging, and production environments, using automated credential rotation, applying configuration injection at runtime, validating secrets before production deployment, maintaining audit logs for credential access, and using environment-specific naming conventions.
How do Docker and Kubernetes handle API key security?
Docker handles API key security through Docker secrets that encrypt credentials and mount them as files in containers, while Kubernetes uses Kubernetes secrets stored in etcd and injected into pods as environment variables or volume mounts. Both systems separate credentials from container images.
When should you use JWT authentication instead of API keys?
You should use JWT authentication when you need short-lived, self-contained tokens with embedded claims and expiration dates, making them safer than permanent API keys. JWTs work well for user-specific authentication, while API keys suit service-to-service authentication where rotation happens through controlled processes.
How do you monitor for exposed API keys in production?
You monitor for exposed API keys by implementing audit logs tracking credential usage patterns, using secrets scanning tools on repositories, setting up rate limiting to detect unusual activity, reviewing access logs for unauthorized requests, and configuring alerts for suspicious authentication attempts or usage spikes.
What’s the principle of least privilege for API key management?
The principle of least privilege for API key management means granting each credential only the minimum permissions required for its specific function through IAM roles and service accounts. This limits damage from compromised keys by restricting what actions unauthorized users can perform.
How do Next.js Server Actions protect API keys?
Next.js Server Actions protect API keys by executing server-side logic while accepting client-side invocations, keeping credentials entirely on the server without exposure in browser code. This approach maintains security while enabling direct communication between client components and server-side operations without separate API route setup.
