JWT Authentication
In this guide, you will register an OIDC identity provider as a trust anchor. You will then bind a caller API key to an external identity and authenticate a request with a JSON Web Token (JWT) the provider issued.
JWT authentication lets agents present a short-lived token from your identity provider instead of a long-lived caller API key. Supported providers include Keycloak, Entra ID, Okta, Auth0, and any OIDC-compliant provider. AISIX verifies the token's signature and claims on every request. It then runs the request as the caller API key whose subject binding matches the token, so the token inherits that key's model access, rate limits, and budget. No API key is ever distributed to the agent.
Dashboard single sign-on (for human operators) and data-plane JWT authentication (for agent requests) are independent. Configuring one does not affect the other.
How It Works
When an environment has at least one enabled OIDC provider, AISIX inspects each request's bearer token:
- If the bearer is a JWT, its
iss(issuer) claim selects the matching trust provider. A token whose issuer matches no enabled provider is rejected. - AISIX verifies the token's signature against the provider's JSON Web Key Set (JWKS), and validates the registered claims:
exp(expiration, always required),aud(audience, matched against the provider's accepted audiences), andnbf(not-before) when present. - The provider's
required_scopesandbound_claimsrequirements are enforced. - The value of the provider's identity claim (
subby default) selects the caller API key whosejwt_subjectequals it and whosejwt_providernames this provider. The request then runs as that key. Scoping the binding to the provider means a second trusted provider cannot mint a token that impersonates this provider's identity.
Anything else — a token that fails verification, or a valid token with no matching key — is rejected before the request reaches a provider, MCP server, or vector store. Signing-key rotation at the identity provider is picked up automatically, with no gateway restart.
Prerequisites
Before starting, prepare the following:
- A running AISIX stack with an environment and an admin token that has the
writescope. Follow the AISIX On-Premises Quickstart if you do not have one yet. - An OIDC identity provider that issues JWT tokens to your agents, and its issuer URL. You also need either its JWKS endpoint URL or its OIDC discovery document (
<issuer>/.well-known/openid-configuration). - A model alias the caller should be allowed to use. If you have not created one yet, configure Provider Credentials and Model Aliases first.
Export the base URL, the admin token, the environment ID, and the model ID:
export AISIX_CP="http://localhost:8080/api"
export AISIX_TOKEN="aisix_pat_YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
export MODEL_ID="YOUR_MODEL_ID"
Register a Trust Provider
Register the identity provider AISIX should trust. At minimum, provide the issuer and the accepted audiences:
curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/oidc_providers" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "corp-keycloak",
"issuer": "https://sso.example.com/realms/agents",
"audiences": ["aisix-gateway"],
"required_scopes": ["ai.access"],
"bound_claims": {
"department": "ai-lab"
}
}' | jq
The response returns the created provider, including the defaults AISIX filled in:
{
"oidc_provider": {
"id": "b2c3d4e5-6789-4abc-def0-123456789abc",
"env_id": "9be9891a-6a53-4bd8-a897-a03fe38a1ca5",
"name": "corp-keycloak",
"issuer": "https://sso.example.com/realms/agents",
"audiences": ["aisix-gateway"],
"identity_claim": "sub",
"required_scopes": ["ai.access"],
"bound_claims": {
"department": "ai-lab"
},
"leeway_secs": 0,
"enabled": true,
"created_at": "2026-07-27T12:00:00Z",
"updated_at": "2026-07-27T12:00:00Z"
}
}
Provider Fields
| Field | Description |
|---|---|
issuer | Expected iss claim, compared exactly against the token. Unique within the environment. |
audiences | Accepted aud values. A token's audience must contain at least one of these. Tokens with no audience claim are rejected. |
jwks_uri | Endpoint the signing keys are fetched from. Omit it to resolve the endpoint from the issuer's OIDC discovery document. |
identity_claim | Claim whose value selects the caller API key, matched against each key's jwt_subject. Dots traverse nested objects, for example resource_access.account. Defaults to sub. |
required_scopes | Scopes that must all be present in the token's scope claim (a space-delimited string or an array). |
bound_claims | Additional claim requirements. Each key names a claim (dots traverse nested objects); the requirement holds when the claim equals — or, for array claims, contains — the expected value or one of the expected values. |
leeway_secs | Clock-skew allowance in seconds for the time-based claims. Defaults to 0. |
enabled | Whether the provider participates in authentication. Defaults to true. |
If you omit jwks_uri, AISIX resolves and caches the signing-key endpoint from the issuer's discovery document. Provide jwks_uri explicitly when the discovery document is not reachable from the gateway.
Bind a Caller API Key to an Identity
Create (or update) a caller API key with two fields set together:
jwt_subject— the external identity a verified token maps to. Set it to the value your identity provider puts in the claim named by the provider'sidentity_claim.jwt_provider— the name of the OIDC provider allowed to assert that subject. Only a token issued by this provider is ever mapped to the key.
curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/api_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "billing-agent",
"allowed_models": ["'"$MODEL_ID"'"],
"jwt_subject": "agent-billing-01",
"jwt_provider": "corp-keycloak"
}' | jq
The jwt_provider and jwt_subject pair is unique within the environment, and both must be set together. Give each agent identity its own caller API key so per-identity model access, rate limits, budgets, and usage attribution all apply independently. You do not need to distribute the key's plaintext value — agents authenticate with their JWT, not the key.
Authenticate a Request
Have the agent obtain a token from your identity provider, then send it as the bearer token on the proxy API exactly like a caller API key:
export AGENT_JWT="eyJhbGciOiJSUzI1NiIsImtpZCI6..."
curl -sS -X POST "http://localhost:9080/v1/chat/completions" \
-H "Authorization: Bearer $AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_MODEL_ALIAS",
"messages": [{"role": "user", "content": "Hello"}]
}' | jq
AISIX verifies the token and maps it to the billing-agent key: its sub claim agent-billing-01 matches the key's jwt_subject, and the token's issuer resolves to the corp-keycloak provider named in the key's jwt_provider. The request then runs under that key's permissions. The token's aud must include aisix-gateway. It must also carry the ai.access scope and the department: ai-lab claim required by the provider.
Rejection Reasons
When a token is rejected, the error response carries a stable error.code so clients can react without parsing messages:
error.code | HTTP status | Meaning |
|---|---|---|
jwt_expired | 401 | The token's exp deadline has passed. Fetch a fresh token. |
jwt_invalid | 401 | The token is malformed, from an untrusted issuer, has a bad signature, or its audience does not match. |
jwt_claims_rejected | 403 | The token is valid but does not satisfy the provider's required_scopes or bound_claims. |
jwt_identity_unmapped | 401 | The token is valid but no caller API key has a matching jwt_subject. Bind a key to the identity. |
jwks_unavailable | 503 | The provider's signing keys could not be fetched. Retry after the identity provider is reachable again. |
Key Rotation
When your identity provider rotates its signing keys, AISIX fetches the new key set automatically the first time it sees a token signed by an unrecognized key. No configuration change or gateway restart is needed. Tokens signed by a retired key stop authenticating once the provider removes that key from its JWKS.
Disable or Remove a Trust Provider
Disable a provider to stop trusting its tokens while keeping its configuration:
export PROVIDER_ID="b2c3d4e5-6789-4abc-def0-123456789abc"
curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID/oidc_providers/$PROVIDER_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": false}' | jq
Delete it to remove the trust entirely:
curl -sS -X DELETE "$AISIX_CP/environments/$ENV_ID/oidc_providers/$PROVIDER_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" | jq
Deleting a provider does not affect the caller API keys or their jwt_subject bindings. Those keys still authenticate with their plaintext values, and can be re-associated with a new provider later.