Skip to main content

Keycloak Integration

This walkthrough connects a Keycloak realm to the gateway end to end: users sign in to Keycloak as themselves, call the gateway with the resulting JWT, and run under a caller API key selected by their department or group — no per-user key distribution, no directory duplication. It is the concrete companion to JWT Authentication (trust and verification) and JWT Claim Mappings (evaluation semantics).

What you build:

  • A Keycloak realm whose access tokens carry the gateway audience, a department claim, and a groups claim.
  • Two policy keys at the gateway — a restricted finance key and an unrestricted platform key.
  • Two claim mappings: members of the platform-admin group run under the platform key; everyone with department = finance runs under the finance key; everyone else is rejected.

The steps were verified against Keycloak 26 (quay.io/keycloak/keycloak:26.3 start-dev). Field names may sit in slightly different places in other versions; the values are what matter.

Step 1: Create the Realm and Client

Create a realm (this walkthrough uses aisix) and an OpenID Connect client in it for the applications that will request tokens:

Client settingValue
Client IDai-clients
Client authenticationOn (confidential — the client has a secret)
Direct access grantsOn, if you want to test with curl password grants as below
Standard flowPer your application's sign-in shape

Note the client secret from the client's Credentials tab.

Step 2: Shape the Access Token

Out of the box a Keycloak access token carries neither the gateway's audience nor your directory attributes. Add three protocol mappers to the client (Clients → ai-clients → Client scopes → ai-clients-dedicated → Add mapper → By configuration):

Mapper typeConfiguration
AudienceIncluded Custom Audience: aisix-gateway, add to access token. The gateway rejects tokens whose aud does not include a configured audience.
User AttributeUser Attribute: department, Token Claim Name: department, claim type String, add to access token.
Group MembershipToken Claim Name: groups, Full group path: Off, add to access token.

One realm-level switch matters on Keycloak 24 and later: custom user attributes such as department are silently dropped unless the realm's user profile knows them. Either declare the attribute (Realm settings → User profile → Create attribute) or set Unmanaged attributes to Enabled on the same screen. Without this, the attribute you type on a user simply does not persist, and the claim never appears in tokens.

Step 3: Create Groups and Users

Create the platform-admin group, then the users:

  • alice — attribute department = finance.
  • bob — member of platform-admin.
  • charlie — neither, to prove the default-deny path.

Give every user a password and a complete profile (email, first name, last name — or relax the profile requirements in the realm's user profile). A user with unmet profile requirements or pending required actions fails a direct-grant login with Account is not fully set up.

Step 4: Trust the Realm at the Gateway

Register the realm as a trust provider. The issuer must equal the token's iss claim byte for byte — for a realm named aisix that is <keycloak base URL>/realms/aisix — and the JWKS URI is discovered from it automatically.

In a resources file:

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

On a managed deployment, create the same provider under Trust Providers in the dashboard, or via the Admin API — the fields are identical (see JWT Authentication).

Step 5: Map Departments and Groups to Policy Keys

Create the two policy keys and the two mappings. The group rule gets the better (lower) priority so platform admins in the finance department still land on the platform key:

api_keys:
- display_name: finance-policy-key
key_env: FINANCE_POLICY_KEY
allowed_models: ["finance-model"]
- display_name: admin-policy-key
key_env: ADMIN_POLICY_KEY
allowed_models: ["*"]

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

contains is the operator for Keycloak's array claims (groups here, or realm_access.roles for realm roles — dots traverse nested objects); exact fits single-valued attributes like department. Evaluation order, tie-breaking, and the interaction with direct jwt_subject bindings are specified in JWT Claim Mappings.

Step 6: Verify

Fetch a token for each user and call the gateway with it as the bearer:

TOKEN=$(curl -s "https://sso.example.com/realms/aisix/protocol/openid-connect/token" \
-d grant_type=password -d client_id=ai-clients -d client_secret="$CLIENT_SECRET" \
-d username=alice -d password="$ALICE_PASSWORD" | jq -r .access_token)

curl "https://gateway.example.com/v1/chat/completions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"model": "finance-model", "messages": [{"role": "user", "content": "hello"}]}'

The three users pin the whole contract:

CallerRequestResult
alice (department = finance)finance-modelSucceeds — the finance-dept mapping selected the finance key.
aliceany other model403 — the finance key's allowed_models governs every request it admits.
bob (platform-admin group)any modelSucceeds — the group rule outranks everything at priority 50.
charlie (no match)any model401 with code jwt_identity_unmapped — no rule matched, and there is no default admission.

Every request's usage is attributed to the individual: usage events and logs carry jwt_subject (Keycloak's sub by default), the provider name, and the matched mapping — see Attribution.

Troubleshooting

SymptomCause and fix
Token endpoint answers Account is not fully set upThe user has pending required actions or an incomplete profile for a direct grant. Complete email/first/last name (or relax the realm's user profile requirements) and clear required actions.
department never appears in the tokenOn Keycloak 24+ the user profile drops undeclared attributes. Declare the attribute or enable unmanaged attributes (Step 2), then re-set the value on the user.
401 with jwt_invalid despite a fresh tokenThe token's aud does not include a configured audience (missing audience mapper), or issuer does not equal the token's iss exactly — scheme, host, and path included.
401 with jwt_identity_unmappedVerification succeeded but no mapping matched: the claim is missing from the token, the operator does not fit the claim's type (exact on an array, contains on a string), or no rule covers the identity. Decode the token (for example at jwt.io) and compare its claims against your match conditions.