Skip to main content

authz-keycloak

The authz-keycloak plugin integrates APISIX and API7 Gateway with Keycloak Authorization Services. It sends the caller's bearer token and requested permissions to Keycloak's User-Managed Access (UMA) token endpoint. Keycloak evaluates its resources, scopes, policies, and permissions before the gateway proxies the request.

Permissions can be selected dynamically or configured explicitly. With dynamic path loading, the gateway uses a Keycloak service account to resolve the request URI through the Protection API. With static permissions, the gateway sends the configured resource and scope names directly to the UMA token endpoint.

Examples​

The following setup creates a Keycloak resource server and demonstrates dynamic and static UMA permission checks.

Before proceeding:

Configure Keycloak​

Start Keycloak, then configure a protected resource, authorization policy, and scope-based permission.

The walkthrough uses a Keycloak service account to obtain test access tokens. A client-scope policy permits tokens that include httpbin-access to access the protected resource with the access authorization scope.

Start Keycloak​

Choose the environment that matches the gateway deployment.

If apisix-keycloak is already running from the Keycloak SSO guide, connect it to the APISIX quickstart network:

docker network connect apisix-quickstart-net apisix-keycloak

Then skip the next command. Otherwise, start Keycloak on the APISIX quickstart network in development mode:

docker run -d --name apisix-keycloak \
--network apisix-quickstart-net \
-e 'KC_BOOTSTRAP_ADMIN_USERNAME=quickstart-admin' \
-e 'KC_BOOTSTRAP_ADMIN_PASSWORD=quickstart-admin-pass' \
-p 127.0.0.1:8080:8080 \
quay.io/keycloak/keycloak:26.7.3 start-dev

Save the Keycloak address that containers on the quickstart network can reach:

export KEYCLOAK_URL=http://apisix-keycloak:8080

Development mode and the example administrator credentials are intended only for local testing. For a production deployment, use HTTPS, a production database, and a permanent administrator account.

Open http://localhost:8080/admin/ and sign in with the administrator username quickstart-admin and password quickstart-admin-pass.

Create a Realm and Resource Server​

Create a realm for the authorization resources:

  1. Select Manage realms → Create realm.
  2. Enter authz-realm as the realm name.
  3. Select Create.

Register a confidential OIDC client as the protected resource server:

  1. Select Clients → Create client.
  2. Keep Client type set to OpenID Connect, enter apisix-authz as the client ID, and select Next.
  3. Turn on Client authentication and Authorization. Leave the interactive authentication flows off and select Save.

Enable client authentication and authorization in Keycloak

Enabling Authorization also enables the client service account and assigns its uma_protection role. APISIX uses that service account to query the Protection API when dynamic path loading is enabled.

Create and Assign a Client Scope​

Create the client scope required by the authorization policy:

  1. Select Client scopes → Create client scope.
  2. Enter httpbin-access as the name and keep Protocol set to OpenID Connect.
  3. Turn on Include in token scope and select Save.
  4. Open Clients → apisix-authz → Client scopes and select Add client scope.
  5. Select httpbin-access, select Add, and add it as an optional client scope.

Assign the optional client scope to the Keycloak client

The token request later includes scope=httpbin-access. Keeping the scope optional also makes the denied-request example reproducible without changing the Keycloak configuration.

Create the Authorization Objects​

Open Clients → apisix-authz → Authorization and create the scope and protected resource:

  1. Open Scopes, select Create authorization scope, enter access, and select Save.

  2. Open Resources, select Create resource, and configure these values:

    FieldValue
    Namehttpbin-anything
    Display nameHTTPBin Anything
    URIs/anything/authz
    Authorization scopesaccess
  3. Select Save.

Create the protected Keycloak resource

Create the policy that requires the client scope:

  1. Open Policies and select Create client policy → Client scope.
  2. Enter httpbin-access-policy as the name.
  3. Select httpbin-access as the client scope and mark it as required.
  4. Select Save.

Create the Keycloak client-scope policy

Connect the resource and authorization scope to the policy:

  1. Open Permissions and select Create permission → Scope-based.
  2. Enter httpbin-access-permission as the name.
  3. Select httpbin-anything as the resource, access as the authorization scope, and httpbin-access-policy as the policy.
  4. Select Save.

Create the Keycloak scope-based permission

Save the Client Credentials​

Open Clients → apisix-authz → Credentials and copy the client secret. Save the client ID and secret as environment variables:

export KEYCLOAK_CLIENT_ID=apisix-authz
export KEYCLOAK_CLIENT_SECRET=replace-with-your-client-secret

Keep the client secret confidential. Store production credentials in a secret manager and rotate them according to the organization's credential-rotation policy.

Request an Access Token​

Request a service-account token with the optional client scope. Run the command for the environment selected earlier.

Run the token request from a temporary container on the quickstart network:

export ACCESS_TOKEN="$(
docker run --rm --network apisix-quickstart-net \
curlimages/curl:8.22.0 -sS \
"${KEYCLOAK_URL}/realms/authz-realm/protocol/openid-connect/token" \
--user "${KEYCLOAK_CLIENT_ID}:${KEYCLOAK_CLIENT_SECRET}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "scope=httpbin-access" | \
jq -er '.access_token'
)"

Authorize Requests by Path​

Dynamic path loading lets APISIX resolve the incoming request URI to a Keycloak resource. Configure a route that queries the Protection API, then asks the UMA token endpoint whether the caller can access the resolved resource.

Choose the API used to configure the route.

Create the route through the Admin API:

curl "http://127.0.0.1:9180/apisix/admin/routes/authz-keycloak" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"uri": "/anything/authz",
"plugins": {
"authz-keycloak": {
"lazy_load_paths": true,
"discovery": "$KEYCLOAK_URL/realms/authz-realm/.well-known/uma2-configuration",
"client_id": "$KEYCLOAK_CLIENT_ID",
"client_secret": "$KEYCLOAK_CLIENT_SECRET"
},
"serverless-post-function": {
"phase": "access",
"functions": [
"return function(conf, ctx) ngx.req.clear_header('Authorization') end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ lazy_load_paths: Resolves the request URI to Keycloak resources through the Protection API instead of using a static permission list.

❷ discovery: URI of the Keycloak UMA discovery document. The plugin obtains the token and resource-registration endpoints from this document.

❸ client_id and client_secret: Credentials of the Keycloak resource-server client. APISIX uses them to obtain the service-account token required by the Protection API.

❹ serverless-post-function: Removes the caller's bearer token after authz-keycloak evaluates it, preventing the sample upstream from receiving the credential. Omit this plugin if the upstream application must receive the token.

Verify Dynamic Authorization​

Send the access token to the protected route:

curl -i "http://127.0.0.1:9080/anything/authz" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"

An HTTP/1.1 200 OK response verifies that Keycloak permitted the token to access the resource. The response body should contain fields similar to these:

{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.7.1",
"X-Amzn-Trace-Id": "Root=1-...",
"X-Forwarded-Host": "127.0.0.1:9080"
},
"json": null,
"method": "GET",
"origin": "192.168.155.1, xxx.xxx.xxx.xxx",
"url": "http://127.0.0.1:9080/anything/authz"
}

Header values and the reported origin address vary by environment. The sample upstream should not receive the Authorization header.

Request another access token without the required client scope.

export TOKEN_WITHOUT_SCOPE="$(
docker run --rm --network apisix-quickstart-net \
curlimages/curl:8.22.0 -sS \
"${KEYCLOAK_URL}/realms/authz-realm/protocol/openid-connect/token" \
--user "${KEYCLOAK_CLIENT_ID}:${KEYCLOAK_CLIENT_SECRET}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" | \
jq -er '.access_token'
)"

Send the token to the route:

curl -i "http://127.0.0.1:9080/anything/authz" \
-H "Authorization: Bearer ${TOKEN_WITHOUT_SCOPE}"

APISIX returns HTTP/1.1 403 Forbidden because the token does not satisfy httpbin-access-policy.

Send a request without a bearer token:

curl -i "http://127.0.0.1:9080/anything/authz"

APISIX returns HTTP/1.1 401 Unauthorized because the request does not contain a token for Keycloak to evaluate.

Authorize Requests with Static Permissions​

Static permissions avoid the Protection API lookup when the required Keycloak resource and scope are known in advance. Configure a route that always asks Keycloak to evaluate httpbin-anything#access.

Choose the API used to configure the route.

Create the route through the Admin API:

curl "http://127.0.0.1:9180/apisix/admin/routes/authz-keycloak-static" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"uri": "/anything/authz-static",
"plugins": {
"authz-keycloak": {
"lazy_load_paths": false,
"permissions": ["httpbin-anything#access"],
"discovery": "$KEYCLOAK_URL/realms/authz-realm/.well-known/uma2-configuration",
"client_id": "$KEYCLOAK_CLIENT_ID"
},
"serverless-post-function": {
"phase": "access",
"functions": [
"return function(conf, ctx) ngx.req.clear_header('Authorization') end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ lazy_load_paths: Set to false to use the configured permission list without querying the Protection API.

❷ permissions: Resource and authorization scope that Keycloak evaluates for every request to this route.

❸ discovery and client_id: Identify the Keycloak UMA token endpoint and the resource server. The static workflow does not require the client secret because APISIX does not call the Protection API.

Verify Static Authorization​

Send the permitted token to the static route:

curl -i "http://127.0.0.1:9080/anything/authz-static" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"

APISIX returns HTTP/1.1 200 OK. Sending TOKEN_WITHOUT_SCOPE instead returns HTTP/1.1 403 Forbidden.

You have now configured Keycloak Authorization Services to enforce dynamic and static permissions at APISIX. See the authz-keycloak configuration reference for additional options, including HTTP-method scopes and access-denied redirects. See the Keycloak Authorization Services Guide for more policy and permission types.