# How to Manage API Keys in a Browser Application: The Vite + Window Pattern

> Securely manage API keys in browser apps using Vite and the window pattern. Learn to inject only essential keys and sanitize user input for robust security.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-09-04

---

**The gods-eye-view project secures sensitive credentials by storing API keys in server-side environment variables, selectively injecting only necessary keys into the browser via Vite's `import.meta.env`, and validating user input through strict sanitization rules.**

Managing API keys in client-side JavaScript presents a fundamental security challenge: browsers cannot keep secrets. The [gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view) repository solves this by isolating keys on the server, deliberately surfacing only the Google Maps API key to the client, and wrapping access in validation layers. This approach ensures that sensitive keys like `OPENAI_API_KEY` and `AISSTREAM_API_KEY` remain server-side while allowing the frontend to access mapping functionality.

## Server-Side Storage and Selective Exposure

The architecture begins with **Vite's environment variable system**. All sensitive keys—including `GOOGLE_MAPS_API_KEY`, `TOMTOM_API_KEY`, `OPENAI_API_KEY`, and `AISSTREAM_API_KEY`—reside in a `.env` file that never leaves the server. The Vite configuration reads these via `process.env.*` and makes selective decisions about client exposure.

**Server-only keys remain protected.** For example, the configuration checks for `AISSTREAM_API_KEY` and returns a 503 status if missing, preventing the application from starting without proper credentials ([vite.config.js#L4894](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vite.config.js#L4894)). Similarly, `OPENAI_API_KEY` is strictly server-side, with endpoints verifying its presence before processing requests ([vite.config.js#L5073](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vite.config.js#L5073)).

**Client-side exposure is explicit.** Only the Google Maps key gets injected into the browser bundle. The build pipeline uses `import.meta.env` to pass this specific variable through, while stripping others from the client build ([vite.config.js#L7779](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vite.config.js#L7779)).

## Runtime Key Management Pattern

### Exposing Keys to the Window Object

The entry point [`src/main.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/main.js) receives the selectively exposed key and attaches it to the global `window` object for downstream consumption:

```javascript
// src/main.js
const googleApiKey = import.meta.env.GOOGLE_MAPS_API_KEY;
if (googleApiKey) {
  window.__GOOGLE_MAPS_API_KEY__ = googleApiKey;
}

```

This pattern appears at lines [81-82](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/main.js#L81-L82), creating a clear contract: browser code expects `window.__GOOGLE_MAPS_API_KEY__` to contain the valid key.

### Runtime Retrieval with Fallback

Client modules implement a defensive retrieval pattern that checks the global window variable first, then falls back to `import.meta.env` for direct access:

```javascript
function getGoogleMapsKey() {
  return window.__GOOGLE_MAPS_API_KEY__ || import.meta.env.GOOGLE_MAPS_API_KEY;
}

// Usage in locations module
export async function fetchLocationData(lat, lng) {
  const key = getGoogleMapsKey();
  if (!key) throw new Error('Google Maps API key not configured');
  
  const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${encodeURIComponent(key)}`;
  // ...
}

```

This retrieval logic appears in [src/locations.js#L350](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/locations.js#L350) and [src/voice/gevActions.js#L1267](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/voice/gevActions.js#L1267).

### Server-Side Proxy Patterns

For keys that must remain hidden, the application uses server-side proxying. The TOMTOM API key, for instance, never reaches the browser. Instead, the Vite dev server constructs URLs using `process.env.TOMTOM_API_KEY` ([vite.config.js#L1891](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vite.config.js#L1891)) and validates its presence before handling requests ([vite.config.js#L1948](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vite.config.js#L1948)).

## Input Validation and Sanitization

When users supply API keys through the interface (for local development or custom deployments), the application enforces strict validation rules defined in [src/keySetupCore.test.mjs](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/keySetupCore.test.mjs). These rules prevent injection attacks and malformed input:

```javascript
function validateApiKey(key) {
  const trimmed = key.trim();
  
  // Reject empty keys
  if (!trimmed) return false;
  
  // Reject keys with internal whitespace
  if (/\s/.test(trimmed)) return false;
  
  // Reject non-ASCII characters
  if (/[^\x00-\x7F]/.test(trimmed)) return false;
  
  // Enforce maximum length (2000 characters)
  if (trimmed.length > 2000) return false;
  
  return true;
}

```

The validation suite at lines [111-118](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/keySetupCore.test.mjs#L111-L118) ensures that any key passed to `window.__GOOGLE_MAPS_API_KEY__` or stored in `.env` meets these security criteria.

## CLI and Development Tooling

Helper scripts in the `tools/` directory read from the same `.env` file but abort with clear error messages if keys are missing. For example, the Street View panorama tool checks for `GOOGLE_MAPS_API_KEY` and exits gracefully if absent ([tools/streetview-panorama.mjs#L74-L90](https://github.com/bilawalsidhu/gods-eye-view/blob/main/tools/streetview-panorama.mjs#L74-L90)). The [tools/README.md](https://github.com/bilawalsidhu/gods-eye-view/blob/main/tools/README.md#L11) documents the requirement to place the Google Maps key in `.env` before running CLI operations.

## Summary

- **Server-side isolation**: Store all API keys in `.env` and restrict client exposure using Vite's environment filtering.
- **Selective browser exposure**: Only expose keys that the browser absolutely requires (e.g., Google Maps) via `import.meta.env` and `window` object assignment.
- **Defensive retrieval**: Implement fallback patterns in client code: `window.__VAR__ || import.meta.env.VAR`.
- **Strict validation**: Sanitize user-provided keys using regex rules that block whitespace, non-ASCII characters, and overflow attacks.
- **Server proxying**: Keep sensitive keys like `OPENAI_API_KEY` and `AISSTREAM_API_KEY` server-side, using Vite middleware or backend endpoints to proxy requests.

## Frequently Asked Questions

### Why can't I expose all API keys to the browser?

**Exposing server-side keys like `OPENAI_API_KEY` or `AISSTREAM_API_KEY` would allow malicious users to extract them from browser DevTools and abuse your quotas or access restricted data.** The gods-eye-view architecture keeps these in `process.env` exclusively, returning 503 errors if they're missing from the server environment ([vite.config.js#L6420-L6421](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vite.config.js#L6420-L6421)), while only the Google Maps key—required for client-side map rendering—reaches the browser.

### How does the application handle missing API keys?

**The build system and runtime perform layered checks.** At build time, Vite validates that required server keys exist. At runtime, client modules check `window.__GOOGLE_MAPS_API_KEY__` and throw descriptive errors if undefined. Server endpoints verify `process.env` variables and return 500-series status codes if credentials are absent, preventing partial application states.

### What validation rules apply to user-provided API keys?

**The validation layer enforces ASCII-only input, rejects internal whitespace, trims leading/trailing spaces, and limits length to 2000 characters.** These rules, tested in [src/keySetupCore.test.mjs](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/keySetupCore.test.mjs#L111-L118), prevent common injection vectors and ensure compatibility with URL encoding when constructing API requests.

### How do CLI tools access the same environment variables?

**Development scripts use Node.js `process.env` to read the `.env` file directly**, bypassing the browser exposure layer. The Street View tool and other utilities instantiate a validation check before executing API calls, ensuring that missing keys trigger immediate, clear error messages rather than runtime failures ([tools/streetview-panorama.mjs#L74-L90](https://github.com/bilawalsidhu/gods-eye-view/blob/main/tools/streetview-panorama.mjs#L74-L90)).