Any key, token or password placed in a mobile app is handed to every person who installs it, and no amount of hiding changes that. Teams still ship payment, AI provider, cloud storage and messaging keys inside apps because the app needs to call those services and a key in the build is the quickest way to make it work. This guide explains why secrets in apps always leak, which credentials can safely ship and which cannot, the architecture patterns that remove the need for embedded secrets, how to stop secrets reaching builds, and what to do when a key is already in a released version you cannot recall. It is for mobile developers, mobile security engineers and the platform teams that own the backend APIs apps call. The OWASP risk itself, platform secure storage and the credential class reference are covered in the OWASP Mobile Top 10 M1 Improper Credential Usage Guide; this page focuses on design choices and operations. Sources were checked on 8 November 2026.
Key takeaways
- The app package is public. Anything compiled in, bundled or downloaded at start-up can be recovered by whoever holds the device; obfuscation raises cost but never makes a secret safe.
- Classify before you fix. Public identifiers can ship, restricted client keys can ship with restrictions, server secrets must never ship.
- Move the privilege, not the key. The backend holds provider secrets; the app proves who the user is and, where possible, that it is a genuine app instance.
- Released builds live for years. A key found in a shipped version must be rotated, and old versions must be retired with a minimum supported version policy.
- Scan the artefact, not just the repository. Secrets often enter at build time through configuration files and SDK set-up, so scan the APK, AAB or IPA before release.
Why keys in apps always leak
A mobile app runs on a device the attacker controls. The package is downloaded from a public store, can be unpacked, and runs in memory that a determined user can inspect. Three consequences follow.
- Static extraction: strings in code, resource files, configuration files and JavaScript bundles in cross-platform apps are readable from the package. Automated tools do this across whole app stores, so a key in a little-known app is found as readily as one in a popular app.
- Runtime extraction: keys that are split, encrypted in the binary or fetched from a server at start-up still exist in memory or in network traffic at the moment the app uses them. Moving a key into native code or downloading it on first launch changes where it appears, not whether it can be recovered.
- Shared identity: an embedded key is the same in every install, so its use cannot be attributed to a user, rate limits apply to everyone at once, and revoking it breaks every copy of the app.
Standards say this plainly. RFC 8252 (OAuth 2.0 for Native Apps, Best Current Practice 212) states that secrets statically included in an app distributed to multiple users should not be treated as confidential, and that an authorisation server which requires such a secret must still treat the app as a public client. OWASP's MASVS resilience category states that anti-tampering and obfuscation must not be used as a substitute for proper security architecture. Google Cloud's API key guidance says to keep keys out of client code and have the client call your server, which adds the credential.
What can ship and what cannot
Every credential found in a mobile project falls into one of these classes. The class decides the action.
| Class | Examples | Ship in the app? | What to do |
|---|---|---|---|
| Public identifier | OAuth client ID, analytics or crash reporting project ID, Firebase app ID | Yes | Treat as public; never use for authorisation decisions |
| Client key designed to be public, with restrictions | Maps or Firebase web API keys | Yes, restricted | Restrict to named APIs and to your app's package and signing certificate or bundle ID; set quotas; enforce rules on the backend |
| Server-side provider secret | Payment provider secret key, AI model provider key, cloud access keys, SMTP or SMS gateway token, push service server key | Never | Move the call behind your backend and rotate any key that has shipped |
| Signing or encryption key | JWT signing secret, symmetric key used to "protect" data sent to the server | Never | Sign and verify on the server; generate device keys on the device in hardware-backed storage |
| OAuth client secret | Confidential client secret copied into a native app | No | Register the app as a public client using authorisation code with PKCE |
| User session tokens | Access and refresh tokens issued at sign-in | Yes, on the device only | Short lifetime, revocable, stored in platform secure storage |
Restricted client keys deserve care. A key meant to be public can quietly gain power if a new service is enabled on the cloud project behind it, so restrict each key to the specific APIs it needs and review restrictions when project services change. See the Google API Keys Security Guide and the Firebase Credentials Security Guide.
Patterns that remove the embedded secret
The fix is architectural: give the app an identity it can legitimately hold, and keep privileged credentials where the attacker cannot reach them.
| Pattern | How it works | Use when | Trade-off |
|---|---|---|---|
| Backend proxy (backend for frontend) | The app calls your API with the user's token; your server adds the provider secret and calls the provider | Payments, AI models, messaging, any provider with a secret key | You run and scale the proxy; you must enforce per-user limits on it |
| Short-lived scoped credentials from a token service | The app exchanges a user or app identity for temporary, narrowly scoped cloud credentials | Direct uploads to object storage or direct calls to cloud services at scale | Policy design is critical: scope by user and resource |
| Signed, time-limited URLs | The backend issues a pre-signed URL for one object and one operation | File upload and download | Each URL must be short-lived and single-purpose |
| App attestation | The platform attests that a request comes from your genuine app on a genuine device; the backend verifies a short-lived token | Reducing abuse of APIs by scripts and modified apps | Raises the bar, does not identify users or replace authorisation |
| User authentication with OAuth and PKCE | The user signs in through an external user agent; the app holds only user tokens | Every app with accounts | Requires a proper authorisation server and token lifetimes |
Two of these are worth showing with concrete platform features. On AWS, Amazon Cognito identity pools exchange a sign-in token from a user pool, a social provider, or an OIDC or SAML provider for temporary AWS credentials through AssumeRoleWithWebIdentity, with separate narrower roles for guest users and roles chosen per user from their claims. On Google platforms, Firebase App Check obtains an attestation from Play Integrity on Android or DeviceCheck or App Attest on Apple platforms, exchanges it for a time-limited token, and lets protected backends reject requests without one; Firebase notes that App Check does not replace authentication and does not guarantee the elimination of all abuse. See the Mobile App Attestation guide and the OAuth for Native and Mobile Apps Guide.
Decision guidance: if the provider charges per call or acts on money, data or messages, use a backend proxy with per-user limits. If the app needs high-volume direct access to cloud storage, use short-lived scoped credentials or signed URLs. Add attestation on top of either when automated abuse is a concern. A small team with one backend can start with the proxy for everything; a large estate typically standardises a token service so each app team does not build its own.
Where identity fits
An embedded key is a machine credential issued to "the app" and shared by every install: a non-human identity with no owner, no attribution and no safe revocation. The patterns above replace it with three identities that can each be governed: the user, proved through sign-in; the app instance, proved through attestation; and the backend service, which alone holds provider secrets in a secrets manager with rotation. Client keys that legitimately ship still need an owner, an inventory entry, restrictions and a rotation plan, like any other NHI. See the API Key Management Guide and the Secrets Management Guide.
Keeping secrets out of builds
Secrets rarely arrive through a developer typing a key into source code on purpose. They come in through build configuration and SDK set-up.
- Build-time injection is still embedding: keys passed as build variables, written into generated configuration or resource files, or placed in a bundled
.envfile end up in the package exactly as if they were hardcoded. Use build variables only for values that are safe to be public. - SDK set-up files: third-party SDKs often ask for a key in a configuration file or initialisation call. Check the SDK documentation for whether the key is designed to be public and restricted, or is a server key that belongs on your backend.
- Debug and test builds: test keys and staging endpoints left in release variants are a common leak. Separate build flavours and configurations, and fail the release build if a debug configuration is present.
- Repository scanning: run secret scanning on every push and pull request, with push protection where your platform offers it. See the Git Secret Scanning Guide.
- Artefact scanning: scan the built APK, AAB or IPA in CI before upload to the store, because this is what attackers will inspect. Include resource files, configuration files and JavaScript bundles. OWASP's MASTG includes test cases for hardcoded cryptographic keys in code, which a mobile assessment should cover. See the Mobile App Security Testing Guide.
When a secret is already in a released app
Unlike a server, a released app cannot be patched in place. Old versions stay on devices and in archives indefinitely, so a leaked key is a lifecycle problem as much as a rotation problem.
- Classify and assess: decide which class the credential is in and what it can do. Check provider logs for use from unexpected sources, volumes or regions.
- Contain at the provider: for a server secret, rotate immediately and revoke the old key; expect old app versions that call the provider directly to break, which is the cost of containment. For a restricted client key, tighten restrictions and quotas first if immediate rotation would break all users.
- Ship the architectural fix: release a version that calls your backend instead of the provider, rather than a version with a new embedded key, which would leak the same way.
- Retire old versions: enforce a minimum supported version on your API and prompt users to update. On Android, Google Play's in-app updates offer an immediate flow, a full-screen update the user must accept before continuing, for updates critical to core functionality, and a flexible flow for non-critical ones.
- Close out: confirm no traffic uses the old key, record the incident, and add the pattern to artefact scanning rules.
For AI provider keys specifically, see the AI model provider API keys leak response guide, and for triaging scanner findings the Developer Secret Scanning Triage guide.
Detection and measurement
Monitor the services embedded keys reach, not only the app:
- Provider usage anomalies: request volume, cost, source networks and user agents for each client key; spikes or non-mobile clients suggest extraction.
- Attestation failures: rising rejected App Check or Play Integrity verdicts indicate scripted or modified clients.
- Old version traffic: share of API calls from app versions below the minimum supported version.
Metrics that show the programme is working:
- Number of server-side secrets found in release artefacts (target zero), measured by artefact scanning on every build.
- Share of client keys with API and application restrictions applied.
- Share of third-party provider calls routed through a backend rather than made directly from the app.
- Time from discovery to rotation for any shipped secret.
- Share of active installs on a version without known embedded secrets.
Framework mapping
| Framework | Reference |
|---|---|
| OWASP Mobile Top 10 2024 | M1 Improper Credential Usage; M7 Insufficient Binary Protections (why obfuscation is not enough) |
| OWASP MASVS | MASVS-AUTH (enforcement on the remote endpoint); MASVS-CRYPTO (key management); MASVS-STORAGE (tokens on the device); MASVS-RESILIENCE (obfuscation is not a substitute for architecture) |
| IETF | RFC 8252 OAuth 2.0 for Native Apps: public clients, PKCE required, embedded secrets not confidential |
| NIST SP 800-53 Rev. 5 | IA-5 Authenticator Management; SC-12 Cryptographic Key Establishment and Management; SA-11 Developer Testing and Evaluation |
| MITRE ATT&CK | T1552.001 Credentials In Files (unsecured credentials found in application files) |
Common failure patterns
- "It is obfuscated": a provider secret encrypted in the binary with the decryption key next to it, or moved into native code, treated as protected.
- Fetching the key at start-up: the app downloads the provider key from a config endpoint, which anyone can call, and holds it in memory.
- New key, same mistake: a leaked key is rotated and the new one shipped in the next release, starting the cycle again.
- Unrestricted "public" keys: a client key with no API restrictions picks up access to a newly enabled paid service.
- Proxy without limits: the backend proxy forwards any request from any signed-in user, so the abuse simply moves to your API.
- No minimum version: old versions keep calling the provider directly with a compromised key long after the fix ships.
Practitioner checklist
- Inventory every credential in each app and classify it as public identifier, restricted client key, server secret, signing key, OAuth secret or user token.
- Remove every server secret and signing key from apps and route those calls through a backend with per-user limits.
- Register native apps as public OAuth clients and use authorisation code with PKCE.
- Restrict every client key to named APIs and to your package and signing certificate or bundle ID, with quotas.
- Use short-lived scoped credentials or signed URLs for direct cloud access, never long-lived cloud keys.
- Add app attestation for high-value APIs and verify tokens on the backend.
- Scan repositories on every push and scan built APK, AAB and IPA artefacts before every store upload.
- Enforce a minimum supported app version and use forced update flows when a shipped secret is retired.
- Monitor provider usage per client key and alert on cost, volume and source anomalies.
Standards and references
- IETF: RFC 8252 OAuth 2.0 for Native Apps
- OWASP: Mobile Application Security Verification Standard
- OWASP MASTG: Use of Hardcoded Cryptographic Keys in Code
- Google Cloud: Best practices for managing API keys
- Firebase: App Check
- AWS: Amazon Cognito identity pools
- Android Developers: In-app updates
Related NHI Mgmt Group resources: OWASP Mobile Top 10 M1 Improper Credential Usage Guide · Mobile App Security Guide · API Key Management Guide · Mobile App Attestation · Leaked secret breaches