AppAIGatewayDocs
ApplicationsAuth policy

Application identity

How an app proves it is your app, with App Attest for iOS or API keys for a server.

The Auth policy page has three levels, and Application identity is the first. It answers one question: is this request coming from my app? An iOS application answers with Apple App Attest. A server answers with an API key.

The level shows its state at a glance: Verified with App Attest, Team or bundle id missing, Verified with API keys, or No active API key.

iOS applications

Only builds signed with your team and bundle ID can call AI providers through this app.

  • Apple Team ID: found in your Apple Developer account under Membership details.
  • Bundle ID: on your target's Signing & Capabilities tab in Xcode.

Apple binds an App Attest key to one install of one app on one device, and the attestation carries a hash of your team ID and bundle ID. The gateway checks that hash, stores the key's public half, and from then on accepts assertions signed by that key: one at each token exchange and, unless the app verifies App Attest on the token exchange only, one on every request. The key never leaves the Secure Enclave.

Keys are per install. A signed-in user who uses your app on a phone and a tablet holds two keys, and a user who reinstalls the app gets a new one; registering a key never replaces another. Each install is counted on its own by the per-install limits.

iOS app environment

Apple runs App Attest in two environments and stamps which one into every attestation. An app accepts one or both, and at least one must stay on.

  • Production: builds signed for TestFlight and the App Store. On by default.
  • Development: builds run from Xcode, which attest in Apple's development environment. Off unless you turn it on.

A development-signed build is debuggable and runs on any device carrying your team's provisioning profile, which is why Development is off by default and why the console warns when you turn it on. Put the opt-in on a development bundle ID, never on the one you ship.

Removing an environment also stops the keys it already admitted: on their next request when the app verifies every request, otherwise at their next token exchange, within the hour. The refusal is 403 attest_failed, and the app registers a fresh key in an environment the app still accepts.

Two things no setting changes. App Attest is unavailable in the Simulator, because it needs the Secure Enclave. And a development build can be made to attest in production instead by setting the com.apple.developer.devicecheck.appattest-environment entitlement to production, which is the better answer when a development build only needs to reach a production-only app.

Verify App Attest on

When the gateway checks the install's App Attest assertion.

  • Every request: each request carries a fresh App Attest assertion, so a gateway token copied off the network is useless without the device. Requires the AppAIGateway Swift package 1.4 or later. The default for new apps.
  • Token exchange: the assertion is checked once per hourly token, and requests carry only the token.

Apps created before this setting was added were set to Token exchange, so their existing installs keep working. Switch to Every request once your clients are on the Swift package 1.4 or later: from then on a request without a proof is refused with 401 assertion_required, which is what every build on an older package receives. Request limits, budgets and usage are counted per install either way. See When App Attest is verified.

In the configuration

"authentication": {
  "type": "apple_app_attest",
  "app_attest": {
    "team_id": "ABCDE12345",
    "bundle_id": "com.example.app.dev",
    "environments": ["production", "development"],
    "verify_on": "every_request"
  },
  "end_user": { "source": "app_install" }
}

environments may be omitted, which means ["production"]. verify_on is every_request or token_exchange, and may be omitted, which means every_request. end_user is required on an iOS app and is covered under User authentication.

Server applications

A server application proves itself with one of its API keys. The card lists every key with its Name, Prefix, Last used and Status.

New key asks for a Key name so you can tell keys apart, then shows the key once in a dialog titled Copy your new key now. The gateway stores only a hash, so there is no way to see the key again. Store it in your backend's secret manager.

Revoke stops a key within a minute. Create the replacement first, move your backend to it, then revoke the old one, and rotation costs no downtime.

Once revocation has propagated, bare-key requests using those keys fail with 401 auth_required. Gateway tokens issued from a revoked key also stop working within a minute. The app's pages show a notice when no active key remains.

How the key is sent

Without signed-in users, your backend sends the key as the bearer credential on every request:

Authorization: Bearer agw_…

With signed-in users, the key is exchanged together with the user's sign-in token for a gateway token instead, and bare keys are refused on requests. See Server applications.

In the configuration

"authentication": { "type": "api_key", "end_user": { "source": "none" } }

end_user is required here too. none means the app has no end users; the other sources are covered under User authentication.

Keys are not part of the configuration. POST /v1/admin/apps/{app}/keys creates one and returns key once; POST /v1/admin/apps/{app}/keys/{key}/revoke revokes it.

On this page