How I Would Fix exposed API keys and missing auth in a React Native and Expo subscription dashboard Using Launch Ready.
If I saw exposed API keys and missing auth in a React Native and Expo subscription dashboard, I would treat it as a production incident, not a cleanup...
Opening
If I saw exposed API keys and missing auth in a React Native and Expo subscription dashboard, I would treat it as a production incident, not a cleanup task. The symptom is usually one of these: anyone can open the app and see paid data, API keys are sitting in the bundle or repo, or the app works until someone copies a request and hits the backend directly.
The most likely root cause is simple: the frontend was built first, auth was postponed, and secrets were placed in Expo env vars or hardcoded into client code. The first thing I would inspect is the actual request path from app to API, because that tells me whether the problem is only UI-level missing auth or a deeper backend authorization failure.
Triage in the First Hour
1. Check whether the dashboard is serving protected data without a valid session.
- Open the app in an incognito browser or fresh emulator state.
- Confirm whether subscription content loads before login.
- Try logout, refresh, and deep links.
2. Inspect the network calls from the app.
- Look for direct calls to third-party APIs from React Native screens.
- Check whether any bearer tokens, secret keys, or long-lived API keys are visible in requests.
- Confirm whether auth headers are missing on sensitive endpoints.
3. Review where secrets live.
- Search `.env`, `app.config.js`, `app.json`, `eas.json`, and source files for keys.
- Check Git history for committed secrets.
- Review Expo public env usage versus server-only env usage.
4. Verify backend enforcement.
- Hit protected endpoints with no token.
- Hit them with an expired token.
- Hit them with a token from another user if you have test accounts.
5. Audit access logs and monitoring.
- Look for unusual spikes in 401, 403, 429, or 5xx responses.
- Check whether any secrets were used from unexpected IPs or regions.
- Review Cloudflare logs if traffic goes through it.
6. Freeze risky changes.
- Stop new releases until auth is fixed.
- Rotate exposed keys before doing anything else.
- Disable any third-party key that may have been bundled into the app.
7. Capture evidence before changing code.
- Save screenshots of unauthenticated access.
- Export logs and build artifacts.
- Record which release introduced the issue.
Here is the kind of quick check I would run first:
grep -RniE "sk_live|api_key|secret|bearer|token" .
If that returns anything suspicious in client-facing code, I assume exposure until proven otherwise.
Root Causes
1. Secrets were placed in Expo client config
- Confirmation: API keys appear in `app.config.js`, `app.json`, or `EXPO_PUBLIC_*` variables used directly by screens.
- Risk: anything shipped to the device can be extracted from the bundle.
2. Backend endpoints trust the frontend too much
- Confirmation: protected routes return data without checking session state, JWT validity, or user ownership.
- Risk: anyone can call the API directly even if the UI hides buttons.
3. Subscription logic exists only in UI state
- Confirmation: access is blocked visually in React Native but not enforced on server-side routes.
- Risk: bypassing UI reveals premium data and features.
4. Auth flow was stubbed during prototyping
- Confirmation: there are placeholder login screens, fake sessions, or hardcoded test users still present in production builds.
- Risk: founders think auth exists because screens look complete.
5. Build artifacts contain old secrets
- Confirmation: EAS builds, OTA updates, or cached bundles still reference removed keys.
- Risk: rotating code alone does not remove what was already shipped.
6. Third-party services are called directly from mobile
- Confirmation: Stripe-like billing helpers, analytics admin APIs, or internal tools are invoked from client code instead of through a server layer.
- Risk: secret exposure plus weak auditability plus abuse risk.
The Fix Plan
My approach is to separate three things immediately: identity, authorization, and secret handling. If those are mixed together inside React Native screens, I would pull them apart before adding more features.
1. Rotate exposed secrets first
- Assume every leaked key is compromised.
- Revoke old keys in provider dashboards.
- Issue new keys with least privilege and narrow scopes.
2. Move all real secrets off-device
- Keep only public configuration in Expo client code.
- Put private API calls behind a backend route or serverless function.
- Never ship admin credentials inside a mobile bundle.
3. Add real authentication at the app entry point
- Require sign-in before loading subscription data.
- Use short-lived tokens with refresh logic if needed.
- Make unauthenticated users land on login or onboarding, not hidden screens.
4. Enforce authorization on every protected endpoint
- Check user identity on each request server-side.
- Verify subscription status on the backend before returning premium data.
- Do not trust client flags like `isPro` or `hasAccess`.
5. Replace direct third-party calls with server-mediated calls
- The mobile app should call your API only.
- Your API should call external services using private credentials.
- This gives you logging, rate limits, revocation control, and safer audits.
6. Clean up build and deployment paths
- Remove stale secrets from EAS profiles and CI variables that do not belong there.
- Rebuild from scratch after rotation so old bundles do not survive in caches.
- Confirm OTA update channels are not serving vulnerable code.
7. Add rate limits and abuse controls
- Put Cloudflare in front of public endpoints if applicable.
- Rate limit login attempts and sensitive actions like subscription checks and password resets.
- Log denied requests without leaking tokens into logs.
8. Tighten environment separation
- Use separate dev, staging, and production credentials for everything externalized by environment variable names only where safe to expose publicly on device; keep private values server-side only.
- Confirm staging cannot read production customer data by mistake.
- Make sure test accounts cannot access live subscriptions unless explicitly intended.
9. Ship with observability
- Add alerts for unauthorized access attempts and sudden spikes in protected endpoint traffic.
- Track p95 latency for auth-related requests so fixes do not slow onboarding below 300 ms p95 unnecessarily on cached paths and 800 ms p95 on cold paths if your backend is doing more work than expected.
- Watch crash reports after release because auth regressions often show up as blank screens or redirect loops.
The safest path is boring: rotate keys, enforce backend auth, remove secrets from client code, then rebuild cleanly. Anything else risks shipping a second incident while trying to fix the first one.
Regression Tests Before Redeploy
I would not redeploy until these checks pass:
1. Unauthenticated access blocked
- Open app fresh without a session.
- Acceptance criteria: no premium dashboard data loads; user sees login or gated screen only.
2. Expired token rejected
- Use an expired session token against protected routes.
- Acceptance criteria: server returns 401 and no sensitive payload leaks.
3. Cross-user access denied ```text test user A cannot read user B subscription data via direct API request
4. Secret scan passes - Search repo and build output for private keys before release:
grep -RniE "sk_live|private_key|secret|token" ./dist ./build .
5. Fresh install test on iOS and Android - Delete app data completely and relaunch on both platforms; - Acceptance criteria: onboarding/auth behaves correctly after cold start. 6. Offline and error states behave safely - Kill network during sign-in; - Acceptance criteria: no infinite spinner, no partial access to paid content, clear retry message shown. 7. Subscription downgrade path works - Cancel or downgrade a test account; - Acceptance criteria: premium areas lock within expected sync window, ideally under 5 minutes if webhooks are healthy. 8. Release artifact review passes - Inspect final EAS/CI build settings; - Acceptance criteria: no private env vars baked into client bundle; only approved public config remains visible to device runtime. 9. Basic security review passes - Confirm CORS rules are strict enough for web surfaces; - Confirm logs do not include raw tokens; - Confirm dependency versions do not include known critical auth vulnerabilities. ## Prevention I would put guardrails around this so it does not happen again. | Area | Guardrail | Why it matters | | --- | --- | --- | | Secrets | Keep private keys server-side only | Prevents bundle extraction | | Auth | Backend checks on every protected route | Stops UI bypasses | | Code review | Require security review for auth changes | Catches trust-in-client mistakes | | Monitoring | Alerts for 401 spikes and unusual traffic | Finds abuse early | | QA | Fresh-install tests on iOS and Android | Exposes missing login gates | | UX | Clear empty states after logout/session expiry | Avoids broken blank-screen flows | | Performance | Cache safe reads; keep auth fast | Reduces churn during login | I also recommend three practical rules: 1. Never use client flags as permission checks: If it says `isSubscribed` in React Native but nothing verifies it server-side, it is just decoration. 2. Never store long-lived secrets where the app can read them: If the device can read it, assume an attacker can too. 3. Never merge auth changes without a rollback plan: A bad login release can block paying users faster than any bug elsewhere. For subscription dashboards specifically, I want login flows to stay under 2 screens deep for returning users and I want protected content to fail closed every time. That means if anything breaks during verification, users should see less data rather than more data. ## When to Use Launch Ready Launch Ready fits when you need this fixed fast without turning it into a months-long rewrite. What I would ask you to prepare before starting: 1. Repository access with deploy permissions only where needed 2. Expo/EAS project access 3. Backend hosting access if there is one 4. Domain registrar access 5. Cloudflare account access if already used 6. Email provider access for SPF/DKIM/DMARC setup 7. A list of all environments: dev, staging, production If you have exposed keys already live in production traffic today, Launch Ready is useful as an immediate containment sprint while we plan any deeper auth rebuild separately later if needed.
flowchart TD A[Leak found] --> B[Rotate keys] B --> C[Block unauth] C --> D[Move secret] D --> E[Rebuild clean] E --> F[Test fresh] F --> G[Deploy] G --> H[Monitor]
## References 1. Roadmap.sh Cyber Security Best Practices https://roadmap.sh/cyber-security 2. Roadmap.sh API Security Best Practices https://roadmap.sh/api-security-best-practices 3. Expo Environment Variables https://docs.expo.dev/guides/environment-variables/ 4. Expo Application Services Build Docs https://docs.expo.dev/build/introduction/ 5. OWASP Mobile Application Security Verification Standard https://masvs.org/ --- ## Take the next step If this is a problem in your product right now, here is what to do next: - **[Use the free Cyprian tools](/tools)** - estimate cost, score app risk, check launch readiness, or pick the right service sprint. - **[Book a discovery call](/contact)** - I will tell you honestly whether you need a sprint or if you can DIY the next step. *Written by Cyprian Tinashe Aarons - senior full-stack and AI engineer helping founders rescue, launch, automate, and scale AI-built products.*
Cyprian Tinashe Aarons — Senior Full Stack & AI Engineer
Cyprian helps founders rescue, secure, deploy, and automate AI-built apps with production-grade engineering, launch systems, and AI integration.