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
- The SDK serializes the final request body and asks
POST /app-integrity/v1/challengefor a challenge bound to the authenticated user (or anonymous), app, HTTP method, exact path and query, and body hash. - 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. - The server validates the store proof and returns a 60-second opaque grant.
- The SDK sends that single-use grant in
X-Loomup-App-Grantwith 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.
[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:
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:
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:
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.