How I Would Fix exposed API keys and missing auth in a React Native and Expo internal admin app Using Launch Ready.
The symptom is usually ugly but simple: someone can open the app, inspect the bundle, and find API keys or service tokens sitting in plain text, while the...
How I Would Fix exposed API keys and missing auth in a React Native and Expo internal admin app Using Launch Ready
The symptom is usually ugly but simple: someone can open the app, inspect the bundle, and find API keys or service tokens sitting in plain text, while the admin screens have no real authentication gate. In business terms, that means anyone with the app, a copied build, or a leaked APK/IPA can access internal data, trigger actions, and create support and security risk.
The most likely root cause is that the app was built fast with Expo and React Native, but secrets were treated like frontend config instead of backend-only credentials. The first thing I would inspect is the shipped bundle and the auth flow: where secrets are stored, whether the app uses server-side authorization at all, and whether admin actions are protected by session checks or just hidden in the UI.
Triage in the First Hour
1. Check the production build artifacts.
- Inspect the Expo config, EAS build settings, and any committed `.env` files.
- Look for hardcoded keys in JS, JSON, `app.config.js`, or environment variables injected into client code.
2. Review what is actually exposed.
- Identify which keys are public by design and which are sensitive.
- Confirm whether any key can write data, access admin APIs, or call third-party services with billing impact.
3. Open the admin app as an unauthenticated user.
- Test every screen from a fresh install or logged-out state.
- Confirm whether routes are merely hidden or truly blocked.
4. Check backend authorization.
- Inspect API endpoints for server-side session validation.
- Verify that every admin route checks role and identity on the server, not just in React Native navigation.
5. Review logs and monitoring.
- Look for unusual API usage, failed login spikes, unauthorized requests, or requests from unknown IPs.
- Check Cloudflare logs if traffic passes through it.
6. Audit recent commits and builds.
- Find when secrets were introduced and whether they reached production.
- Check if a hotfix was already attempted but only removed UI links instead of fixing authorization.
7. Verify connected accounts.
- Rotate any exposed third-party credentials immediately if they have write access.
- Check email provider, database admin users, analytics tools, storage buckets, and payment or messaging accounts.
8. Capture current blast radius.
- List affected environments: dev, staging, production.
- Note whether mobile stores already have a live build with embedded secrets.
grep -RInE "sk_live|secret|api[_-]?key|token|bearer" . \ --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist
Root Causes
1. Secrets were placed in client-side environment variables.
- Confirmation: find sensitive values in `app.config.js`, `expoConstants`, or bundled JS output.
- Why it happens: founders assume `.env` means private everywhere. In Expo apps, anything shipped to the device is not secret.
2. The app relies on hidden screens instead of real auth.
- Confirmation: unauthenticated users can still call protected endpoints directly or navigate to admin routes manually.
- Why it happens: UI gating feels like security during development but does nothing against direct API access.
3. Backend endpoints trust client-provided flags.
- Confirmation: requests succeed when you toggle `isAdmin`, `role`, or similar fields from the client payload.
- Why it happens: teams move fast and forget that authorization must be enforced server-side on every request.
4. A third-party service key has too much privilege.
- Confirmation: one key can read and write data across multiple projects or environments.
- Why it happens: developers reuse one key for convenience instead of using least privilege per environment.
5. Secrets were committed into source control or build history.
- Confirmation: git history shows keys even after they were deleted from current files; old builds still contain them.
- Why it happens: deleting a line is not enough once a secret has been shipped.
6. There is no deployment boundary between internal and public surfaces.
- Confirmation: internal admin APIs are reachable from the same public domain without extra protection like Cloudflare Access or backend session enforcement.
- Why it happens: teams launch one app first and add internal controls later, which usually means never.
The Fix Plan
My fix plan is to stop exposure first, then rebuild auth correctly without breaking the app again. I would not try to patch this by hiding buttons or renaming variables; that only delays the next incident.
1. Rotate exposed secrets immediately.
- Revoke any leaked API keys, tokens, service accounts, SMTP credentials, webhook secrets, and database passwords that can affect production data or billing.
- Replace them with new values stored only on trusted backend infrastructure or in secure deployment secrets.
2. Remove all sensitive values from the mobile client.
- Anything inside React Native or Expo should be treated as public once shipped.
- Keep only non-sensitive identifiers in the app, such as public project IDs or base URLs if needed.
3. Move privileged operations behind an authenticated backend layer.
- The mobile app should call your API with a session token or signed JWT after login.
- The backend must validate identity and role on every protected endpoint before returning data or performing writes.
4. Add a real login flow for internal users.
- For an internal admin app, I would use email magic link, SSO if available, or password plus MFA depending on team size and urgency.
- If this is only for staff use during early stage operations, I would still enforce per-user accounts and audit logs.
5. Put network-level protection in front of internal surfaces where possible.
- If this app is truly internal-only web/admin infrastructure behind APIs, I would use Cloudflare Access or equivalent controls for browser-based surfaces and restrict origin access tightly for APIs.
- For mobile apps alone this is not enough by itself, but it adds another barrier for supporting dashboards and admin portals.
6. Separate public config from private config cleanly.
- Use environment variables only on servers and build pipelines where they do not get bundled into client code as secrets.
- In Expo/EAS builds, review every variable that gets injected into the JS bundle before shipping.
7. Lock down authorization by role and resource ownership.
- Admin users should only access endpoints intended for their role level.
- If there are multiple tenants or teams inside one system, verify tenant ID on every query so one account cannot see another account's records.
8. Add rate limiting and abuse controls now rather than later.
- Even an internal tool can be brute-forced if exposed publicly by mistake again.
I would add rate limits to login endpoints and sensitive actions like resend invite, export data, reset password, delete user, or trigger sync jobs.
9. Clean up old builds and distribution links. If an insecure build was shared through TestFlight-like channels or direct APK distribution, I would replace it with a new version immediately so stale installs stop being trusted entry points.
10. Add audit logging before redeploying to production again Log who accessed what admin action, when, from which IP, with enough detail to investigate misuse without leaking sensitive payloads into logs
Regression Tests Before Redeploy
Before I ship this fix, I want proof that both exposure paths are closed: the secrets are gone, and unauthorized users cannot reach protected functions
Acceptance criteria:
- No sensitive secret appears in any mobile bundle,
source file, commit diff, build artifact, or runtime log
- Unauthenticated users cannot open protected screens,
fetch protected records, export data, mutate state, or trigger admin actions
- Authorized users can still complete core workflows without extra friction
- Failed auth attempts are logged without exposing tokens or passwords
- Role checks fail closed if session data is missing,
expired, malformed, or tampered with
QA checks I would run: 1. Fresh install test
- Install the latest build on a clean device
- Confirm logout state blocks all admin routes
- Try deep links into protected screens
2. Direct API test
- Call each protected endpoint without auth headers
- Confirm you get 401 or 403 consistently
3. Role test
- Log in as standard staff
- Confirm staff cannot perform super-admin actions
4. Secret scan test
- Search bundles,
build output, CI logs, env files, release notes, crash reports for leaked keys
5. Negative path test
- Expire sessions manually
- Tamper with JWT claims
- Retry requests after token revocation
6. Smoke test on release candidate
- Login
- View list pages
- Open detail pages
- Perform one safe write action
- Verify analytics,
notifications, sync jobs still work
For an internal admin app like this, I would target at least 90 percent coverage on auth-related service tests, with zero known critical exposures before redeploying
Prevention
The best prevention is to make this mistake hard to repeat even under deadline pressure
Guardrails I would put in place:
- Code review rule: no secret value may live in React Native client code unless it is explicitly public
- Security checklist for every PR:
auth required? server-side authorization? input validation? least privilege? logging safe?
- Secret scanning in CI using automated checks before merge
- Branch protection so production deploys require review from someone who understands auth risk
- Separate environments with separate credentials for dev,
staging, and prod
- Minimal permissions for third-party APIs so one leaked key cannot reach everything
- Session expiry plus refresh strategy so stolen tokens age out quickly
- MFA for staff accounts if humans are logging into anything sensitive
I would also improve UX so security does not become a support burden: show clear login states, session expiry messages, and empty/error states that explain what happened without exposing internals
On performance, auth should not slow down normal use: I would keep p95 login response under 300 ms on backend calls where possible, and make sure added security checks do not bloat startup time by shipping unnecessary libraries into Expo bundles
When to Use Launch Ready
Launch Ready fits when you need me to stop exposure fast and turn a shaky internal app into something safe enough to keep using within 48 hours.
I handle: domain setup, email routing, Cloudflare configuration, SSL, deployment cleanup, secrets handling, environment variables, production deployment checks, uptime monitoring setup, and handover documentation
This sprint makes sense if you already have:
- A working React Native plus Expo codebase
- Access to your hosting account,\nEAS,\nCloudflare,\nbackend,\nand repo\ncredentials\n- A list of third-party services currently connected to the app
- One decision maker who can approve secret rotation quickly
What I need from you before kickoff: 1. Repo access plus current branch status 2. Expo/EAS project access 3. Backend/API access if separate from mobile codebase 4. Cloudflare domain access if DNS sits there 5. A list of all integrations using keys today 6. The exact user roles that should exist inside the admin app
If your issue includes exposed keys plus missing auth,\nI will usually recommend Launch Ready first,\nthen a second sprint for deeper auth redesign if needed.\nThat keeps you from paying twice for avoidable firefighting later\nand reduces support load after release\nby closing both technical risk paths together\nin one controlled handoff\nwith clear rollback steps\nand monitored deployment visibility\n## References
https://roadmap.sh/cyber-security https://roadmap.sh/api-security-best-practices https://roadmap.sh/code-review-best-practices https://docs.expo.dev/guides/environment-vars/ https://cloudflare.com/learning/security/what-is-zero-trust/
Delivery Map
---
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.