Skip to main content

JWT Claim Mappings

JWT Authentication binds one external identity to one caller API key through the jwt_subject field. That fits a fixed fleet of agents, but not an enterprise identity provider asserting departments, groups, or applications for hundreds of people: registering a key per person duplicates the IdP's directory, and changes there never propagate.

Claim mappings close that gap. A mapping is a rule that matches the verified claims of a JWT — a department name, membership in a group — and resolves the request to an existing caller API key. Everyone the rule admits runs under that key's model and tool access, rate limits, and budget, while each request's usage is still attributed to the individual identity from the token.

Claims only ever select a key an operator already created. No claim value becomes configuration: a token cannot name an upstream, widen a model allowlist, or mint a budget. A token matching no mapping is rejected.

How Mappings Are Evaluated

For each request with a JWT bearer, after the full trust-provider verification described in JWT Authentication:

  1. If a caller API key binds the token's subject directly (jwt_subject + jwt_provider), the request runs as that key. The direct binding is authoritative for its subject — including a disabled key, which stays a rejection. Mappings never override it.
  2. Otherwise, the enabled mappings whose jwt_provider names the matched trust provider are evaluated in priority order — lower values first, ties broken by name — and the first mapping whose match conditions all hold selects the key.
  3. If no mapping matches, the request is rejected with jwt_identity_unmapped. There is no default admission.

The match field of a mapping is a list of conditions, all of which must hold:

FieldDescription
claimClaim to inspect. Dots traverse nested objects (for example realm_access.roles). A missing claim never matches.
opexact — the claim must be a string equal to one of values. contains — the claim must be an array containing one of values among its string items (non-string items are ignored). A claim whose type does not fit the operator never matches.
valuesAccepted values; the condition holds when any one matches.

The mapped key must exist in the same environment: a mapping whose key was deleted rejects matching tokens instead of admitting them, and the trust provider named by jwt_provider must verify the token before any rule is considered.

Prerequisites

The workflow below extends the JWT Authentication setup: a registered OIDC trust provider, a model, and curl + jq. You also need a caller API key to act as the shared policy key — the carrier of the model access, rate limits, and budget the admitted identities inherit.

Export the base URL, admin token, environment ID, and the policy key's ID:

export AISIX_CP="http://localhost:8080/api"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
export POLICY_KEY_ID="YOUR_API_KEY_ID"

Create a Claim Mapping

Admit everyone whose department claim equals finance under the policy key:

curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/claim_mappings" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "finance-dept",
"jwt_provider": "corp-keycloak",
"priority": 100,
"match": [
{"claim": "department", "op": "exact", "values": ["finance"]}
],
"resolve": {"api_key_id": "'"$POLICY_KEY_ID"'"}
}' | jq

The response echoes the created mapping. When some gateways in the environment run a release that predates claim mappings, it also carries a warnings array — those gateways skip the rule entirely and keep rejecting the identities it should admit, so upgrade them before relying on it.

Mapping Fields

FieldDescription
nameMapping name, unique within the environment and fixed at creation. Also the evaluation tie-break for equal priorities.
jwt_providerName of the OIDC provider whose tokens this mapping applies to. A mapping never matches a token verified by a different provider, so two providers cannot select each other's keys.
priorityEvaluation order among the provider's mappings: lower values are evaluated first. Defaults to 0.
matchClaim conditions, all of which must hold (see the table above).
resolve.api_key_idID of the caller API key matching requests run as.
enabledWhether the mapping participates in evaluation. Defaults to true; a disabled mapping is kept but skipped.

Update a mapping with PATCH .../claim_mappings/{claim_mapping_id} (the name is fixed — delete and recreate to rename) and remove it with DELETE. Changes take effect on new requests without a gateway restart; identities admitted only by a deleted or disabled mapping stop authenticating as soon as the gateway picks up the change.

In the Dashboard, the environment's Claim mappings page manages the same rules and lists them in evaluation order.

Layer Mappings with Priorities

Rules compose by priority. Give a narrower rule a lower value so it wins for the identities it describes, and let a broader rule catch the rest. The example below resolves to a second policy key — export its ID first:

export ADMIN_KEY_ID="YOUR_ADMIN_API_KEY_ID"

curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/claim_mappings" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "platform-admins",
"jwt_provider": "corp-keycloak",
"priority": 50,
"match": [
{"claim": "realm_access.groups", "op": "contains", "values": ["platform-admin"]}
],
"resolve": {"api_key_id": "'"$ADMIN_KEY_ID"'"}
}' | jq

A token carrying both department: finance and the platform-admin group now resolves through platform-admins (priority 50) rather than finance-dept (priority 100). Different teams needing different policies get their own policy keys — one rule each.

Per-Identity Usage Attribution

Every request admitted through a mapping records three attribution fields on its usage event, visible in the environment's Logs, filterable, and included in the CSV export:

FieldDescription
jwt_subjectThe token's identity-claim value — who ran the request, even though many identities share the policy key.
jwt_providerThe trust provider that verified the token.
jwt_claim_mappingThe mapping that admitted the request. Empty for identities bound directly via jwt_subject.

The Logs page filters by JWT subject, and the jwt_subject / jwt_claim_mapping query parameters on the usage-events API narrow programmatic reads the same way. Configured observability exporters receive the same fields — note that the subject is personal data when your identity claim carries names or emails, so review where exporters deliver before enabling them alongside claim mappings.

Open-Source AISIX Gateway Configuration

In a declarative resources.yaml, reference the policy key by its display_name with the resolve.api_key shorthand:

resources.yaml
oidc_providers:
- name: corp-keycloak
issuer: https://sso.example.com/realms/agents
audiences: ["aisix-gateway"]

api_keys:
- display_name: finance-policy-key
key_env: FINANCE_POLICY_KEY
allowed_models: ["gpt-4o-prod"]

claim_mappings:
- name: finance-dept
jwt_provider: corp-keycloak
priority: 100
match:
- claim: department
op: exact
values: ["finance"]
resolve:
api_key: finance-policy-key

The file loader resolves the reference at load time and rejects unknown provider or key names, a condition list that is empty, or a mapping whose jwt_provider names no provider in the file — a typo fails the load instead of silently never matching.

Rejection Behavior

Mapped authentication extends the JWT rejection table. A verified token that matches no mapping — or matches one whose key no longer exists — is rejected with jwt_identity_unmapped (HTTP 401) before any provider, MCP server, or vector store is reached. The gateway's aisix_auth_decisions_total metric distinguishes the finer-grained reasons (jwt_identity_unmapped, claim_mapping_target_missing, jwt_binding_ambiguous) for operators.

Next Steps