AppAIGatewayDocs
How it works

Identity and entitlement

The three questions the gateway asks about a request, and which combinations an app can be configured for.

Every request answers up to three questions, and each app's Auth policy page is organised around them.

QuestionAnswered bySet under
Is this my app?Apple App Attest, or an application API keyApplication identity
Which user is it acting for?A sign-in token, the app install itself, or a header from your backendUser authentication
Has that user paid?A claim in the sign-in tokenSubscription check

The first question always has an answer. The second and third are optional, and the third needs the second.

Application identity

An iOS application proves itself with Apple App Attest. The device holds a key in the Secure Enclave, Apple attests that the key belongs to a genuine install of your app, and the gateway checks that attestation against your Team ID and Bundle ID. The app then exchanges an assertion for a gateway token that lasts one hour and, by default, signs every request it sends with that same key. There is no shared application API key to embed in the app.

A server application proves itself with an API key, a long-lived agw_… secret. Without signed-in users it sends that key on every request; with signed-in users it exchanges the key and the user's JWT for a gateway token. The key belongs in a secret manager, never in a client.

The AppAIGateway Swift package does the App Attest exchange for you. See iOS with the Swift package.

User authentication

Identifying users is what makes per-user limits, per-user usage and blocking possible. An app can identify them in one of these ways.

Choice in the consoleApp typeWho the user is
Signed-in users onlyiOS and serverThe subject of a sign-in token from your identity provider, verified by the gateway
Unauthenticated usersiOSThe app install. The App Attest key is the identity
Your backend sends the user idServerWhatever your backend puts in a header you choose, taken on trust
No user identityServerNobody. Requests are not linked to a user and per-user limits do not apply

Signed-in users only is the strongest choice. Your app sends the ID token it already has from Firebase, Supabase, Auth0, Clerk, Sign in with Apple or your own issuer, and the gateway verifies the signature, the issuer and the audience against your configuration before it trusts the user ID inside.

Unauthenticated users is the quick start for an iOS app with no sign-in. Each install counts as one user, so per-install limits and blocks work. The identity does not survive reinstalling the app, so a determined user can shed a block by starting over.

Your backend sends the user id belongs on a server you control. The gateway takes the value as given, so it is never appropriate for a client an end user runs.

Details and the settings for each identity provider are on User authentication.

Subscription check

With signed-in users, the gateway can also require that the sign-in token carries a claim saying the user has paid. The gateway never talks to the App Store itself. Something else, typically RevenueCat's Firebase extension or your own backend, writes the entitlement into the user's token, and the gateway checks for it on every token exchange.

Because the claim is written after the purchase, there is a short window where a paying user holds a token without it yet. The gateway reports that as a distinct outcome so your app waits instead of asking the user to sign in again. See Subscription check.

What the gateway token is

An iOS app, or a server app with signed-in users, does not send its user's sign-in token on every request. It exchanges its proof and that token once at the app's token endpoint and receives a gateway token, valid for one hour, that stands for both. Requests carry that token and an X-App-Version header. The Swift package refreshes it before it expires.

A server app with no signed-in users skips the exchange and sends its API key directly.

When App Attest is verified

Each iOS app has a Verify App Attest on setting, verify_on in its configuration, with two values.

ValueWhat the gateway checks
Every request (every_request, the default)The assertion at each token exchange, and a fresh assertion on every request
Token exchange (token_exchange)The assertion at each token exchange only; requests carry the gateway token alone

Either way the gateway token names the install it was issued to, so request limits, budgets and usage are counted per install in both modes.

Apps created before this setting was added were set to Token exchange, so installs already shipped keep working. A new app, or a configuration that leaves the field out, verifies every request.

Every request

With Every request, an iOS app's gateway token is bound to the App Attest key of the install it was issued to, and is not enough on its own. Every request sent with it also carries an X-AppAttest-Proof header: an App Attest assertion that the key signed over that one request. The proof covers:

  • your app's ID, the HTTP method, and the URL path and query exactly as sent;
  • a hash of the request body;
  • a hash of the gateway token itself;
  • the device's timestamp and a random value.

The gateway checks the signature against the install's registered key, and the assertion's counter against every counter that install has already used, so each proof is accepted once. The timestamp is part of what is signed, but the gateway does not compare it with its own clock: a device whose clock is wrong is not refused, and the counter is what stops a replay.

What someone who copies a gateway token off the network, with an HTTPS inspector for example, can do with it: nothing. Without the device's key they cannot sign a new request, a proof they copied alongside it covers only the request it came with, and that request has already been served. A request without a proof is refused with 401 assertion_required.

The Swift package 1.4 or later signs every request for you. See Per-request proof.

Token exchange

With Token exchange, the assertion is checked once per hourly token, and requests carry only the token. A token copied off the network works from anywhere until it expires, at most an hour. Choose it when the builds in use are on a Swift package older than 1.4; switch to Every request once they have been updated.

Combinations

App typeUser authenticationSubscription check
iOSSigned-in usersAvailable
iOSUnauthenticated usersNot available, there is no token to check
ServerSigned-in usersAvailable
ServerBackend sends the user idNot available
ServerNo user identityNot available

Two pairings do not exist. An iOS app cannot name its own user in a header, because the device is the party being limited. A server app cannot use the app-install identity, because it has no attested key.

On this page