skip to content

A researcher extracts a maps API key from your React Native taxi app's bundle; how do you respond, and where should such keys live?

level: seniorimportance: must knowfreq 45%

answer

  1. assume compromised, plan rotation
  2. old installs keep the old key
  3. native map SDK key vs server calls
  4. restrict, cap and monitor client keys
  5. authenticated, rate-limited backend proxy

basics

~20 s

Treat the key as public: split it into a device-side map-rendering key that is restricted and capped, and move routing, geocoding and fare calls behind an authenticated backend proxy, then rotate on a schedule that old installs can survive.

solid answer

~40 s

First classify what the key does. A native map view calls the provider straight from the device, so a **map-rendering key has to ship**; make it low-value by restricting it to your app's identifiers where the provider supports it, limiting it to the APIs the map needs, capping its quota and alerting on spikes. Server-grade calls — directions, geocoding, fare estimates — move behind **your backend**: the app sends the user's session token, and the server authenticates, rate-limits per account, validates input and attaches a key the app never sees. Then rotate carefully: installed versions keep sending the old key until users update, so revoking it at once breaks them. Ship the new version, watch adoption, and revoke once the old key's traffic has drained, sooner if abuse forces it.

code

typescript · 25 lines
typescript
type FareQuote = { amountCents: number; currency: string; etaSeconds: number };

// The app never sees the routing provider's key: it asks its own backend,
// which authenticates the rider, rate-limits and calls the provider.
export async function getFareQuote(
  pickup: { lat: number; lng: number },
  dropoff: { lat: number; lng: number },
  sessionToken: string,
): Promise<FareQuote> {
  const res = await fetch('https://api.taxi.example/v1/fare-quote', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${sessionToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ pickup, dropoff }),
  });
  if (res.status === 429) {
    throw new Error('Too many quote requests, try again shortly');
  }
  if (!res.ok) {
    throw new Error(`Quote failed with status ${res.status}`);
  }
  return (await res.json()) as FareQuote;
}

go deeper

for a junior

Recall that any key inside the app is public, so the fix is to limit what it can do and move sensitive calls to a server the app talks to.

for a middle

Explain the split between a device key the native map SDK needs and server keys used for routing or geocoding, and what a backend proxy does for each call.

for a senior

Lead the response: classify uses, restrict and cap the device key, design an authenticated rate-limited proxy, and plan rotation around installed versions and forced updates.

for a principal

Own the policy: which vendor keys may ever ship in clients, how keys are owned and rotated across apps, and what abuse budget you accept for device keys.

## The scenario A taxi app built with React Native shows a live map, searches addresses, draws routes and estimates fares. One key covers all of it, read from an `EXPO_PUBLIC_` variable and inlined into the bundle. A researcher unzips the APK, runs `strings` over `assets/index.android.bundle` and reports the key. Nothing was "hacked": the key was published the day the app shipped. ## Step 1: classify the key's uses Not every call can move to a server, so start by splitting the uses. | Use | Who calls the provider | Can it move server-side? | |---|---|---| | Rendering map tiles in a native map view | the native map SDK on the device | no — the SDK needs a key on the device | | Address search and geocoding | your JavaScript via `fetch` | yes | | Route and ETA calculation | your JavaScript via `fetch` | yes | | Fare estimation that combines routes and pricing | your JavaScript today | yes, and it should, since pricing is business logic | Moving fare estimation server-side matters twice over: it hides the routing key, and it stops a patched client from quoting itself a cheaper ride, because the price the rider accepts is the one the server computed. The outcome is usually **two keys**: a device key that is public by nature, and a server key that never leaves your backend. ## Step 2: make the device key low-value A device key will always be extractable, so reduce what it can do: - **Restrict it to your app** where the provider supports it, typically by Android package name with signing-certificate fingerprint and by iOS bundle identifier. - **Limit it to the map APIs** the native view needs, so it cannot call geocoding or routing. - **Cap its quota** and set billing and usage alerts. - **Monitor** traffic by platform and version. Be honest about the limit: app restrictions rely on identifiers the client presents, so a determined attacker can imitate them. They stop casual reuse and billing abuse from other apps; they do not make the key secret. ## Step 3: put the server-grade calls behind a proxy A **backend proxy** is an endpoint of your own that performs the third-party call on the app's behalf. To be worth having it must: 1. **Authenticate** the caller with the user's session, not with a shared app key. 2. **Rate-limit per account and per device**, so one stolen session cannot run up the bill. 3. **Validate input** and expose only fixed operations — "route from A to B" — never a pass-through of arbitrary provider URLs. 4. **Hold the provider key** in the server's own secret storage and **cache** repeated lookups. 5. Return **only** the fields the screen needs. A proxy that forwards any request without authentication just moves the leak to your own endpoint. ## Step 4: rotate without breaking the fleet Mobile rotation differs from rotating a server credential because old binaries stay installed. - Ship a release that uses the new device key and the proxy for server calls. - Watch the share of traffic still using the old key; users update slowly, and some never do. - **Revoke** the old key once its traffic has drained, or sooner if abuse forces it, accepting that very old versions lose maps. A minimum-version check with a forced-update screen shortens that window. - An OTA update can swap an inlined JavaScript value for builds that accept it, but putting a new key in the bundle just republishes it; use updates to move calls to the proxy. ## Step 5: stop it recurring - Classify every embedded value in review: public by design, or server-only. - Scan the **built release bundle**, not just the repository, for known key formats before submitting. - Keep an owner and a rotation runbook for every client key. ## What interviewers listen for - Recognising that the **map SDK key must ship**, rather than promising to hide everything behind a proxy. - Knowing that restrictions and quotas limit **abuse**, not **exposure**. - Treating **rotation on mobile** as a fleet problem, not a one-click revoke. - Thanking the researcher, confirming the fix window with them, and checking provider logs for past abuse before calling the incident closed. - Rejecting obfuscation as the fix.

  • Why is a backend proxy that simply forwards any request to the provider URL the app supplies a bad fix?
    It turns your server into an open relay: anyone who reads the bundle can call it and spend your quota exactly as they did with the raw key, and a caller-chosen URL invites server-side request forgery. A useful proxy exposes fixed operations, authenticates the user, rate-limits per account and validates every input.
  • Could you fetch the device map key from your backend at launch instead of embedding it?
    It keeps the key out of the bundle file but not out of the app: the key sits in memory, in the network response and in whatever the map SDK stores, so a device owner can still read it. It does let you rotate the device key without a release, which is a genuine benefit, but restrictions and quotas remain the real control.
  • Which signals tell you the extracted key is being abused rather than just exposed?
    Usage growing faster than active riders, calls to APIs your app never makes, traffic from regions or platforms you do not serve, and requests missing your app's identifiers where the provider reports them. Quota alerts turn those signals into a page before the bill arrives.

saying these in an interview costs you the question

  • Obfuscating the bundle is an adequate fix for a leaked key.
  • Revoking the old key immediately is harmless because users auto-update.
  • Every provider key can be moved behind the backend, including the map SDK key.
  • Restricting a key to the app's package name makes it secret.
  • A proxy is safe as long as its URL is not documented.