Loomup Docs
Guide /docs/app-integrity

Mobile app integrity

Loomup mobile apps never contain a service key or provider credential. Normal user JWTs remain the authorization principal and continue through resource rules. App Attest and Play Integrity add a short-lived proof that an allowlisted store build prepared the exact mutation request.

Request flow

  1. The SDK serializes the final request body and asks POST /app-integrity/v1/challenge for a challenge bound to the authenticated user (or anonymous), app, HTTP method, exact path and query, and body hash.
  2. The native provider obtains App Attest or a standard Play Integrity token using the returned request hash and sends it to POST /app-integrity/v1/verify.
  3. The server validates the store proof and returns a 60-second opaque grant.
  4. The SDK sends that single-use grant in X-Loomup-App-Grant with the exact mutation. The server atomically claims it before normal authorization.

Grants cannot be replayed or moved to another user, method, target, or body. Verified backend service keys bypass this app-origin gate for trusted workers, but still use their existing scopes. A string that merely looks like a service key does not bypass it.

Manifest configuration

Start in audit, inspect the aggregate integrity counters in admin metrics, then move to enforce. off is the default.

The allowlist is deployment configuration, not a secret and not a safe additive schema edit. Local and self-hosted projects can change [app_integrity] in loomup.app.toml and deploy it through the normal manifest path. Hosted project managers can use Studio's Mobile SDK page or the manager-only @loomup/cli commands below. These dedicated controls update only the app-integrity section, audit the change, and reload that project; Studio's Resource schema publisher still cannot change app identities or enforcement.

toml
[app_integrity]
mode = "audit"

[app_integrity.apps.ios_main]
platform = "ios"
team_id = "ABCDE12345"
bundle_id = "com.example.app"
app_apple_id = 1234567890
distribution = "app_store_or_testflight"

[app_integrity.apps.android_main]
platform = "android"
package_name = "com.example.app"
cloud_project_number = 123456789012
signing_certificate_sha256 = ["0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"]

For a hosted project, the equivalent scriptable setup is:

console
loomup app-integrity set-ios --project PROJECT_ID --app-id ios_main \
  --team-id ABCDE12345 --bundle-id com.example.app --apple-app-id 1234567890
loomup app-integrity set-android --project PROJECT_ID --app-id android_main \
  --package-name com.example.app --cloud-project-number 123456789012 \
  --certificate-sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
loomup app-integrity set-mode --project PROJECT_ID --mode audit
loomup app-integrity status --project PROJECT_ID

These hosted commands require loomup auth login or LOOMUP_PLATFORM_TOKEN; project and workspace API keys cannot change the policy. Manifest app IDs are portable identifiers of at most 128 characters: start with an ASCII letter or underscore, then use only letters, numbers, and underscores. Use --allow-development only for a separate development identity. Start in audit and review metrics before selecting enforce.

Apple verification accepts App Store and TestFlight transactions and production App Attest keys. Development proofs are disabled unless an individual app entry sets allow_development = true; never enable that on a production manifest.

Android production verification requires a Google service-account JSON file with access to the Play Integrity decode API. Install it out of band:

console
loomup app-integrity put-google-credential ./google-play-integrity.json --config loomup.toml
loomup app-integrity status --config loomup.toml

The credential is validated, written mode 0600 below .loomup/secrets/, and never returned by status. Project exports, clones, logs, and manifest bundles do not include that directory. Delete it with loomup app-integrity delete-google-credential; deletion is not recoverable. Hosted managers can upload the same file through the Mobile SDK page or run:

console
loomup app-integrity put-google-credential --project PROJECT_ID \
  --file ./google-play-integrity.json

The platform console exposes equivalent authenticated GET/PUT/DELETE endpoints at /projects/{id}/app-integrity/google-credential and audits metadata only.

enforce startup fails for an Android app when the credential is missing, so a misconfigured rollout fails closed rather than silently weakening protection. Enforcement applies to every non-admin mutation in the project. If one project also serves browser clients, those browser mutations will not have native store proofs; keep the policy in audit, separate the mobile backend, or route those trusted mutations through an authenticated backend service key before enabling enforce.

Social login keeps this boundary narrow. POST /auth/oauth/authorize and POST /auth/oauth/exchange are ordinary application mutations and require an app grant in enforcement mode. Only the exact Google, Apple, and GitHub provider callback paths are exempt because those requests originate at the external identity provider. Callback state, nonce, provider PKCE, verified identity, and allowed-redirect checks still apply.