API7 Gateway 3.10.9: Require the Whole Keycloak Permission Contract

November 2, 2026

Products

Key Takeaways

  • API7 Gateway 3.10.9, released on October 29, 2026, changes Authz Keycloak from accepting a combined positive decision to checking the complete permission list returned by Keycloak.
  • A route that requests several permissions or scopes now forwards a request only when every requested resource and scope is granted. A partial grant returns HTTP 403, or redirects to access_denied_redirect_uri when configured.
  • With http_method_as_scope: true, the request method is an additional required scope even when the static permission already lists scopes. Scope names are also sent without surrounding spaces.
  • A successful token-endpoint response that cannot be decoded now becomes HTTP 503; explicit 401, 403, and other error responses remain distinct failure paths.
  • Inventory multi-permission routes and run both allow and deny tests against a 3.10.9 canary. During a mixed-version rollout, older and newer Data Planes can make different decisions for the same partial grant.

An authorization rule is a contract, not a bag of optional hints. If a payment route requires both payments#view and payments#refund, a caller who holds only view does not satisfy the route's declared policy—even if the identity provider can return a positive decision for part of the request.

That boundary is the focus of the Authz Keycloak breaking change in API7 Gateway 3.10.9. Earlier behavior asked Keycloak to decide on all requested permissions together and forwarded the request when at least one was granted. The 3.10.9 Data Plane instead asks for the granted permission list and checks every requested item before the request can reach the upstream.

This is a security-correctness improvement, but it is also an availability change for callers whose grants never matched the complete route configuration. The safe upgrade path is to make the permission contract visible, test its negative cases, and update either the Keycloak grants or the route requirements before the new Data Plane enters production rotation.

sequenceDiagram
    participant C as API Client
    participant G as API7 Gateway 3.10.9
    participant K as Keycloak
    participant U as Upstream API
    C->>G: Bearer token + request
    G->>K: Requested resources and scopes
    K-->>G: Granted permission list
    G->>G: Compare every requested item
    alt Complete grant
        G->>U: Forward request
        U-->>C: Upstream response
    else Missing resource or scope
        G-->>C: HTTP 403 or configured redirect
    end

The diagram shows the decision boundary, not the whole Keycloak policy lifecycle. Keycloak still evaluates its policies; API7 Gateway checks whether the returned grant list covers the route's complete request.

Authorization Must Be a Conjunction, Not a Partial Match

The affected configurations have a common shape: the route asks for more than one authorization condition.

{ "authz-keycloak": { "token_endpoint": "https://keycloak.example/realms/platform/protocol/openid-connect/token", "client_id": "payments-api", "permissions": [ "payments#view", "payments#refund" ], "http_method_as_scope": false } }

On 3.10.9, the plugin requests Keycloak's permissions response mode, then matches each requested resource by name or ID and checks all of its scopes. Only a complete match continues through the gateway.

The change also closes two less obvious gaps:

  1. permissions: ["payments#view, refund"] now requires both view and refund. The spaces around scope names are removed before the request to Keycloak, so a scope whose first character was a space before refund is no longer ignored as a differently named scope.
  2. When http_method_as_scope is enabled, the HTTP method is appended to the static permission's scopes. A DELETE request for payments#view therefore needs both view and DELETE; view alone is insufficient.

These are runtime checks, not Dashboard-form checks. Seeing the expected fields in the Control Plane proves that the route was configured; it does not prove that a limited caller is denied or that the full-grant caller reaches the upstream.

Read the Result Matrix Before You Upgrade

The corresponding public Apache APISIX change adds a pinned integration test for a caller with a limited grant and a caller with the complete fixture grant. Its plugin CI shard ran that test and finished with Result: PASS.

Route requirementFixture grantRequestObserved status
view and delete permissionsview onlyGET403
view and delete permissionsBoth permissionsGET200
Same permissions in reverse orderview onlyGET403
One permission with view, delete scopesview onlyGET403
One permission with view, delete scopesBoth scopesGET200
view plus method scopeview and GETGET200
view plus method scopeview onlyDELETE403
view plus method scopeview and DELETEDELETE200

This matrix is runtime evidence for the stated fixture, not proof that an organization's Keycloak policies express its intended business rules. The 200 rows show that the fixture reached its upstream with the complete grant; the 403 rows show that the partial grant stopped at the gateway.

Inventory the Contracts That Can Change

Before upgrading, search route and service configurations that enable Authz Keycloak and classify these shapes:

  • more than one entry in permissions;
  • more than one comma-separated scope in an entry;
  • http_method_as_scope: true together with static scoped permissions; and
  • scope strings with leading or trailing spaces.

For every affected route, build a small contract table with the resource, required scopes, HTTP methods, Keycloak role or policy that grants them, and at least one caller expected to be denied. Do not simply grant every missing permission to preserve an old 200: first confirm that the route's declared contract is intentional.

Test both permission orderings when generated configuration can reorder arrays. The 3.10.9 comparison is order-independent, but exercising the generated result catches tooling that accidentally drops or rewrites an entry before it reaches the gateway.

Separate Denial, Redirect, and Authorization-Service Failure

Not every non-200 response means the same thing:

  • Incomplete grant: The gateway returns HTTP 403 with the access-denied response. When access_denied_redirect_uri is configured, it returns the existing HTTP 307 redirect instead.
  • Invalid or expired caller token: A 401 response from the Keycloak token endpoint remains 401.
  • Other token-endpoint error: Responses at or above 400 are returned through their corresponding error path.
  • Undecodable successful response: If the token endpoint answers successfully but the permission list cannot be decoded, the gateway returns HTTP 503. It does not treat malformed data as authorization.

Monitor these paths separately. A rise in 403 after the canary begins serving traffic points to a grant/requirement mismatch. A 503 points to the authorization-service response or integration path. Combining them into one generic “Keycloak failure” alert makes the upgrade harder to diagnose.

The Authz Keycloak reference documents the configuration surface, and the official 3.10.9 release notes are the API7 product/version evidence. The corresponding public Apache APISIX source change and review provide inspectable implementation and test evidence for the same documented behavior; the public commit hash is not presented here as the API7 3.10.9 binary's source revision.

Roll Out the New Contract Without Intermittent Decisions

The corrected comparison runs in the Data Plane. During a rolling upgrade, a 3.10.8 instance can still accept a partial grant that a 3.10.9 instance denies. If both versions receive production traffic, the same token and route can therefore appear to fail intermittently depending on which instance handled the request.

Use the API7 Gateway rolling-upgrade procedure with an authorization-specific gate:

  1. Deploy a 3.10.9 canary outside the production load balancer.
  2. Apply the same route configuration and use controlled test identities with full and partial grants.
  3. Verify the upstream receives only the full-grant requests; check the client-visible 403, 307, 401, and 503 paths separately where they apply.
  4. Correct unintended route requirements or Keycloak grants, then rerun the negative tests.
  5. Add the canary to production traffic and monitor status codes by Data Plane version before expanding the rollout.
  6. Remove older Data Planes only after the complete grant matrix passes and expected callers no longer depend on partial authorization.

API7 Gateway 3.10.9 makes the route's Keycloak permission contract literal: every requested resource and scope must be present in the grant returned by Keycloak. Treat that as an opportunity to replace assumed authorization with a small, repeatable contract suite. Review the full 3.10.9 release notes, validate the affected routes on a canary, and then evaluate API7 Enterprise for centrally managed gateway policy and operations.

Tags:
Share article link