Skip to main content

Keycloak

Enable the JWT Authorization Grant identity-provider and client settings in Keycloak. Configure AIGW with private_key_jwt: an RFC 7523 JWT bearer authorization grant for the user assertion plus JWT client authentication for the AIGW client.

Prerequisite: Keycloak with the jwt-authorization-grant feature available. Keycloak 26.5 introduced it as a preview feature. On 26.5, include jwt-authorization-grant in your build's enabled feature list:

bin/kc.sh build --features=jwt-authorization-grant

Preserve any other features enabled by your deployment. Check your version's feature configuration before rebuilding and restarting. Standard Token Exchange alone does not enable this grant.

Before you start​

Collect these values from the AIGW Resource:

  • AIGW Resource authorization-server/issuer URL, for example https://api.aigw.loginid.ai/<RESOURCE_ID>;
  • Resource JWKS URL https://api.aigw.loginid.ai/<RESOURCE_ID>/.well-known/jwks.json;
  • client ID configured in the AIGW Resource;
  • requested scopes;
  • your Keycloak realm issuer and token endpoint.

The AIGW Resource Reference page exposes the merchant-facing values.

AIGW reference

Step 1 — Create a JWT Authorization Grant Identity Provider​

In Keycloak go to Identity providers → Add provider → JWT Authorization Grant.

Add provider

Configure:

FieldValue
AliasUnique alias for this AIGW Resource, e.g. aigw-search-api
IssuerAIGW per-resource issuer
Use JWKS URLOn
JWKS URLAIGW per-resource JWKS URL
Max allowed assertion expiration1 hour in this example; for assertions with iat, this limits their age when received
Assertion signature algorithmES256
Allow assertion reuseOff in the reference setup
Allowed clock skew60 seconds in the reference setup

JWT grant IdP form

After saving, verify that the provider exists and still points to the intended Resource issuer/JWKS.

IdP created

One IdP per AIGW Resource in this pattern

The AIGW issuer is per Resource. A Keycloak JWT Authorization Grant IdP configured for Resource A should not be reused for Resource B with a different issuer.

Step 2 — Create the Keycloak client used by AIGW​

Go to Clients → Create client and choose OpenID Connect.

Exact match required

The Client ID must exactly match the Client ID configured on the AIGW Resource.

In Capability config:

  • Client authentication: On
  • Standard flow: Off for this back-channel client
  • JWT Authorization Grant: On
  • Allowed Identity Providers for JWT Authorization Grant: the IdP alias from step 1
  • Use JWT Authorization Grant for this setup; Standard Token Exchange is not required by this request pattern

Client capability

Step 3 — Configure Signed JWT client authentication​

Open the client Credentials tab:

  • Client Authenticator: Signed JWT
  • Signature algorithm: ES256
  • Max expiration: 1 minute; send each newly issued client assertion within that window. AIGW assertions include iat and expire after 5 minutes by default; Keycloak also checks exp.

Signed JWT credentials

When Keycloak asks Change to client-jwt?, confirm the change. Otherwise the new authenticator is not saved.

Next open Keys:

JWKS

Using a JWKS URL lets Keycloak fetch an updated key when AIGW rotates to a new kid.

Step 4 — Configure the custom audience mapping​

AIGW user/client assertions use the AIGW Resource's configured Issuer URL (the merchant AS issuer) as aud.

Keycloak accepts its realm issuer or token endpoint as the user-assertion audience. When AIGW's Issuer URL equals the Keycloak realm issuer, no custom audience mapping is needed. If your integration requires a custom accepted audience, open Advanced → OpenID Connect Compatibility Modes → Custom audience mapping and map:

<JWT Authorization Grant IdP alias> -> <Keycloak realm issuer>

Example:

aigw-search-api -> https://id.example.com/realms/merchant

Audience mapping

Step 5 — Create and attach merchant scopes​

For every AIGW Requested scope, create a Keycloak client scope with the same protocol value.

Example for search:read:

  • Name: search:read
  • Type: None
  • Protocol: OpenID Connect
  • Include in token scope: On

Create scope

Then open the AIGW client and attach the scope as Optional.

Attach scope

Using Type None avoids assigning the scope as a realm-wide default to unrelated clients.

Step 6 — Point the AIGW Resource to Keycloak​

Back in AIGW, open the Token exchange Resource Configuration and set:

Issuer URL = https://<keycloak>/realms/<realm>
Token endpoint = https://<keycloak>/realms/<realm>/protocol/openid-connect/token
Client ID = <the Keycloak client created above>
Method = private_key_jwt

AIGW Resource configuration

Reload after saving and verify the values persisted.

Step 7 — Define user matching/provisioning​

Keycloak resolves the assertion's sub through a federated identity link. Matching the user's Keycloak email field alone does not create that link.

For the first test, use a merchant test user whose identity you have verified. AIGW tries emails returned by the user's sign-in provider before its pairwise subject. Link the exact email supplied by the test user's AIGW sign-in provider as the external user ID; do not guess it from a display name or an unrelated Keycloak username.

  1. In Keycloak, open Users, select the merchant test user, and copy its internal user ID.
  2. Copy the JWT Authorization Grant provider's Alias from Step 1, e.g. aigw-search-api.
  3. From an administrator's terminal, create the link with the Admin REST API. Supply KEYCLOAK_ADMIN_TOKEN from your existing administrative session; it needs permission to manage this user.
KEYCLOAK_BASE='https://id.example.com'
REALM='merchant'
KEYCLOAK_USER_ID='your-keycloak-user-id'
IDP_ALIAS='aigw-search-api'
EXTERNAL_USER_ID='developer@example.com'

: "${KEYCLOAK_ADMIN_TOKEN:?Set your Keycloak administrative access token}"
curl --fail-with-body --silent --show-error \
-X POST \
-H "Authorization: Bearer $KEYCLOAK_ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
--data "$(jq -nc --arg provider "$IDP_ALIAS" --arg external "$EXTERNAL_USER_ID" \
'{identityProvider:$provider,userId:$external,userName:$external}')" \
"$KEYCLOAK_BASE/admin/realms/$REALM/users/$KEYCLOAK_USER_ID/federated-identity/$IDP_ALIAS"

Use Bash/Zsh with curl and jq. Replace the example email with the verified external identity. A successful create returns HTTP 204 with no body. If a link already exists (409), read it and confirm its values before changing anything.

Read the link back:

curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $KEYCLOAK_ADMIN_TOKEN" \
"$KEYCLOAK_BASE/admin/realms/$REALM/users/$KEYCLOAK_USER_ID/federated-identity" \
| jq --arg provider "$IDP_ALIAS" '.[] | select(.identityProvider == $provider)'

Expect identityProvider to match the IdP alias and userId to match the exact asserted external identity. Sign in through AIGW with that test identity, authorize search:read, and redeem the merchant access token.

For subject-based links, obtain the Project's pairwise subject from a validated AIGW identity token or the Account's authorized user view and use it as EXTERNAL_USER_ID. Automatic provisioning requires a separate merchant-owned workflow; the JWT grant does not create users.

See the Keycloak user-link API for permissions and response codes.

Step 8 — Validate the setup​

Check each layer separately:

  1. Keycloak can fetch the AIGW Resource JWKS.
  2. The Keycloak client uses Signed JWT/ES256.
  3. The assertion audience matches the realm issuer or the configured custom mapping.
  4. Every requested AIGW scope exists and is attached.
  5. The target user can be resolved from the assertion sub.
  6. AIGW Resource issuer/token endpoint/client ID exactly match Keycloak.
  7. Assertions have not expired, and their age since iat is within Keycloak's configured limit.

Keycloak's JWT Authorization Grant does not issue a merchant refresh token. When that access token expires and AIGW returns 410 reauthentication_required, start a new user authorization.

Common Keycloak pitfalls​

ProblemWhy it happensFix
IdP alias collisionAdd-provider form may prefill jwt-authorization-grantUse a unique alias per AIGW Resource
New scope never appearsInclude in token scope defaults OffTurn it On
Scope accidentally assigned widelyClient scope Type set to Default/Optional during creationUse Type=None, then attach explicitly
invalid_clientSigned JWT/JWKS/algorithm/lifetime mismatchRecheck client credentials + keys
invalid_grant / user not foundNo user mapping/link for assertion subConfigure mapping/provisioning
JWT rejected for audienceIssuer URL or custom mapping differs from assertion audMatch the exact realm issuer; configure a custom mapping only when required
Key rotation failsKeycloak has static key instead of JWKS URLConfigure the Resource JWKS URL