federated-enforcement.idpartners.global
Open source · Apache-2.0 · ID Partners

Federated enforcement.

OpenID AuthZEN enforcement points for Kong, PingAccess, Envoy, Istio, agentgateway and Node. Each one puts an AI agent's request to a Policy Decision Point before it goes through - and learns which PDP from what the resource publishes, or from a federation whose word outranks it. When the answer is no, the agent gets a challenge it can resolve, not a 403.

Enforces at
Kong, PingAccess, Envoy, Istio, agentgateway, and inside a Node service
Speaks
OpenID AuthZEN 1.0, to any conformant PDP
Authorises
REST calls and MCP tool calls, per call
Finds the PDP
from the resource's published metadata, or the federation's word
On a deny
a challenge the agent can resolve, in the header, the body and the MCP error
When a PDP is down
closed by default, open only where you say, and never quietly
why

An agent is not a session

An AI agent calling a tool is one agent, acting for one human, touching one resource, right now. Whether that is allowed depends on all three, and it belongs in policy, not in tool code. AuthZEN gives an enforcement point a standard way to put that question to a decision point. These are the enforcement points.

It acts under delegation

The token an agent carries names two parties: the agent, and the customer it acts for. A gateway that only checks "is this token valid" cannot tell whether this agent may do this for this customer. The policy has to see the delegation, not just the bearer.

The decision depends on the request

A balance enquiry and a payment over ten thousand dollars arrive on the same connection with the same token. One is fine on a password; the other needs the customer's approval, or a verified credential. That is a policy decision, made per request.

A flat 403 stops the agent dead

A human reads an error page. An agent needs to know what would make the request succeed: which scope to obtain, which credential to present, whether anyone is logged in at all. A deny that says how to resolve it is the difference between an agent that completes the task and one that gives up.

what federated means here

The gateway reads who decides. It does not decide.

Most enforcement points are told where their PDP is and stay pointed there. These read it per resource: from the resource's own metadata, or, when the resource belongs to an OpenID Federation, from a trust chain that runs back to an anchor you installed by hand. Then they hand the PDP the resource's own requirements and let the PDP do the matching. The gateway compares nothing.

agent / MCP client enforcement point your API or MCP server request + delegated token permit, with who acted for whom deny: a challenge the agent can resolve 1 which PDP decides for this resource? the resource's own document RFC 9728 · self-asserted the federation's word OpenID Federation 1.0 · authoritative taken at its word, and labelled so resolved through the anchor's policy: it wins 2 names the PDPs, the layers in front, and what the resource requires estate PDP · first the resource's PDP AuthZEN 1.0 evaluation · every layer must permit · the first that does not is the answer 3 the PDP matches the token to what the resource published. The enforcement point compares nothing.
Orange is the decision path. The enforcement point never sits in the data path: a permitted request is proxied on with the delegation recorded in headers, and the engine only ever answers "may this go through".
Authoritative

The federation's word

OpenID Federation 1.0 trust chain

The resource's metadata as it survives every superior's policy, signed back to a trust anchor you configured. The federation operator can pin which PDPs may decide for a resource, put an estate PDP in front of every member, and set an authentication floor no member can lower by editing its own document.

Self-asserted

The resource's own document

RFC 9728 protected resource metadata

What the resource publishes about itself: which PDP decides for it, the layers in front, the scopes it uses, the acr it expects. Good enough inside one trust domain, forwarded to the PDP verbatim and labelled as self-asserted.

Always there

The PDP you configured

a static URL

Where a resource that publishes nothing lands, and the whole of today's behaviour if you turn discovery off. The static PDP is the only one that ever receives the configured API key.

  • The federation's word wins. When a trust anchor is configured, the gateway resolves the resource's chain and never reads the resource's own document.
  • An invalid chain fails closed. A resource that claims membership and cannot prove it gets a 503, never a fall-back to its own word or to the static PDP.
  • Allowlists on every call. A discovered PDP or metadata URL outside the allowlist is refused, whoever named it, and a refusal never falls through to a weaker source.
  • The PEP forwards, the PDP matches. The resource's requirements reach the PDP as a document, with the endpoint hit and the token. Policy lives in one place, and the deny stays resolvable because the PDP's reason names the requirement and where it read it.
  • A metadata outage is not an authorisation outage. Metadata is cached with stale-while-failing, refresh throttling and negative caching, so a down resource does not put a fetch in every request's path.
  • No guessed paths. Endpoints come from the PDP's own AuthZEN metadata; a batch is only sent to a PDP that advertises a batch endpoint.

Layered policy

A route names an ordered list of PDPs, or the resource publishes one in authzen_policy_layers, or the federation adds its PDP to every member's. An estate-wide PDP that judges the token and the client goes first; the resource's own PDP after it. Every layer must permit. A layer may be marked fail-open, for the advisory PDP that is not worth an outage, and a permit that skipped one says so in a response header. A deny never opens.

# a route's layers: the estate PDP, then whatever the resource's document names
pdp_layers: "https://estate.example fail-open, resource"

The gateway is the resource's federation face

A federated resource publishes two things: a signed entity configuration the trust controller onboards, and its RFC 9728 metadata. The gateway is the resource's public face, so it publishes both. The split is deliberate: the gateway holds a key, the controller holds the policy. What the federation resolves is what the gateway republishes, signed, so a plain RFC 9728 consumer reads the federation's word without knowing a federation is behind it.

The demo shows the flip: a gateway's document goes from self-asserted to the controller's word when the anchor onboards it, and nothing on the gateway changes.

how a request goes through

One request, whichever edge it reaches

  1. The agent calls

    A REST endpoint or an MCP tool, with a delegated token: the customer is the subject, the agent is the actor.

  2. The enforcement point reads it

    Validates the token and the DPoP proof, reads who is acting for whom, and maps the request, or the tool call's arguments, into an AuthZEN evaluation.

  3. It finds the PDP

    From what the resource publishes, or what the federation resolved for it, and asks each layer of policy in turn.

  4. The PDP decides

    On the delegation, the request, the resource's own requirements and whatever risk signals it holds. Permit, deny, or deny with advice.

  5. The agent goes on, or resolves

    A permit is proxied upstream with the delegation recorded in headers. A deny comes back as a challenge: step up, prove identity, or log in.

what the agent gets back

A deny the agent can act on

Three challenge types, the same three words from every enforcement point, in the HTTP header, the JSON body and the MCP error. An agent resolves them without a human reading prose.

resource_authorisation

The customer approves this scope at the authorisation server, an RFC 9470 step-up. The agent retries with the scope.

identity_proofing

The customer presents a verified credential from their wallet, an mDL. The policy that asked for it is satisfied on the retry.

authn

There is no authenticated user yet. login_required, with the acr to log in at.

step up · insufficient_scope
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
  error="insufficient_scope",
  scope="payments:approve"

{ "error":  "insufficient_scope",
  "scope":  "payments:approve",
  "reason": "payments over 1000 need the
             customer's approval" }
The RFC 9470 challenge. The app steps the customer up for that scope, and the same request goes through.
prove identity · identity_verification_required
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
  error="identity_verification_required",
  doctype="org.iso.18013.5.1.mDL"

{ "error":   "identity_verification_required",
  "doctype": "org.iso.18013.5.1.mDL",
  "reason":  "no verified identity on file
              for account origination" }
The policy asked for a document type. The wallet presents it; nothing about the retry is different but the evidence.
in MCP · JSON-RPC error
HTTP/1.1 200 OK

{ "jsonrpc": "2.0", "id": 7,
  "error": {
    "code": -32001,
    "message":
      "insufficient_scope scope=payments:approve",
    "data": { "authz_challenge": {
      "type":  "resource_authorisation",
      "scope": "payments:approve",
      "pep":   "mcp-edge" } } } }
The same challenge as the COAZ-MCP binding carries it: the prose for a client that string-matches, the data member for one that parses.
four surfaces, and a fifth

Deploy where your traffic already is

Each enforcement point takes the same per-route knobs, spelled the same way: REST or MCP style, whether a token is required, whether it must be DPoP-bound, which resource is guarded, which layers of policy to ask. Configure it in whichever gateway's own terms, and read every knob in the configuration reference.

gateways/kong/authzen-pdp · Lua

Kong Gateway

A native plugin on a route or service, DB-less or with a database. Request mapping, discovery and challenges in Lua; DPoP and MCP tool calls delegated to the engine.

luarocks install kong-plugin-authzen-pdp-0.4.0-1.all.rock
gateways/pingaccess/authzen-pdp · Java

PingAccess

An Add-on SDK rule on an application's API policy, configured in the PingAccess console. Reads the identity PingAccess validated, and verifies the customer's token itself. Drops into deploy/; no PingAccess upgrade.

cp authzen-pdp-pingaccess-<version>.jar $PA_HOME/deploy/
gateways/envoy · ext_authz

Envoy, Istio, agentgateway

No plugin to install. The gateway attaches its external authorisation filter to the engine, and per-route settings travel as context extensions. One binary serves all three; the agentgateway and Istio attachments are in the tree.

docker pull ghcr.io/id-partners/coaz-pep:0.4.0
sdk/node · TypeScript

Your own Node service

For a service that is its own enforcement point: Express middleware for REST and a guard for MCP tool calls, with the same client, verdict and challenge shapes. No runtime dependencies. An unmatched route is a deny.

npm install @id-partners/authzen-pep
gateways/kong/sideband-pdp · Lua

Kong, in front of PingAuthorize's Sideband API

The same discovery, the same layers and the same failure rules, where the policy owns the request mapping and the deny. For an estate that already speaks Sideband and wants the federation's word on who decides.

luarocks install kong-plugin-sideband-pdp-0.4.0-1.all.rock
demo · hosted, or docker compose

See all of it run

A stub trust anchor, resources that are and are not members, two banks' PDPs, an estate PDP and a rogue one that permits everything. A console runs one request through three discovery modes and shows what each read, forwarded and decided. Profiles add Kong and PingAccess in front of the same resource.

the engine

Everything with a specification behind it, implemented once

coaz-pep is one Go binary with two front doors: an Envoy ext_authz gRPC service and an HTTP check API. It carries the parts a gateway plugin cannot safely reimplement, so there is one evaluation and one challenge, not four that drift. The Kong plugin and the PingAccess rule call it for what needs a JOSE library; the Node SDK can delegate to it.

Authorises the tool call, not the endpoint

For MCP the request is one HTTP POST. The engine reads the JSON-RPC body, finds the tool, and authorises that call under the COAZ framework and its MCP binding: the MCP server's tool list declares how a call's arguments and the token's claims become an AuthZEN question. A mapping cannot name a different subject than the token carries. Every method is decided; an unknown one is denied.

Delegation in the decision

The agent is the subject, the customer it acts for travels with it, and the context carries the customer token's scope, authentication level, any rich authorisation details and the audience. The policy reasons about the delegation, not just the bearer.

Token and sender-constraint verification

Access and user tokens verified against your JWKS, with issuer and audience checks. DPoP proofs (RFC 9449) verified, so a stolen token is useless without the key. Every surface delegates this to the one implementation.

Refuses to run insecurely by accident

It will not start without its check-API token, its upstream allowlist, and the JWKS, issuer and audience to verify tokens against, plus the discovery allowlists when discovery is on, and it names every missing one at once. PEP_ALLOW_INSECURE=true is the hatch, logged every time, for development and demos only.

# the image, amd64 and arm64, signed with cosign, with an SBOM and build provenance
docker run --rm -p 9191:9191 -p 9192:9192 \
  -e AUTHZEN_URL=https://pdp.bank.example/tenants/bank-a -e AUTHZEN_API_KEY=… \
  -e CHECK_API_TOKEN=… -e MCP_UPSTREAM_ALLOWLIST=https://mcp.bank.example \
  -e ACCESS_TOKEN_JWKS_URL=… -e ACCESS_TOKEN_ISSUER=… -e ACCESS_TOKEN_AUDIENCE=… \
  ghcr.io/id-partners/coaz-pep:0.4.0

Every environment variable, the per-route knobs, readiness, the SIGTERM drain, audit logs and metrics: the coaz-pep reference.

get started

From the demo to your gateway

  1. See it

    The hosted demo, or cd demo && docker compose up --build -d && ./demo.sh. Press Discover; pick member; watch the federation column deny what the resource column believed.

  2. Pick a surface

    The gateway your traffic already flows through, or the SDK where a service must enforce for itself. Each one's install and every knob is in the reference.

  3. Give it a PDP

    Any AuthZEN 1.0 PDP. Ours put that face on PingAuthorize: a Go proxy, or in-process as a Server SDK servlet. Start static, with AUTHZEN_URL.

  4. Turn discovery on

    Publish the resource's RFC 9728 document with authzen_policy_decision_points, or join a federation and let the anchor say. Set the allowlists; nothing off them is ever asked.

standards in play

Built on standards, and one parameter of our own

Everything here has a specification behind it, except the parameter that names a resource's PDPs in RFC 9728 metadata: authzen_policy_decision_points is this project's, a single constant in each codebase, provisional pending a working-group profile. authzen_policy_layers is its companion.

  • OpenID AuthZEN 1.0 the evaluation contract, and the PDP metadata at /.well-known/authzen-configuration
  • COAZ the AuthZEN framework and its MCP binding: tool-call authorisation, per call
  • RFC 9728 protected resource metadata, scopes_supported, signed_metadata
  • OpenID Federation 1.0 entity configurations, subordinate statements, trust chains, metadata policy
  • RFC 9470 step-up authentication challenges
  • RFC 9449 DPoP, sender-constrained tokens
  • RFC 8707 resource indicators: the identifier discovery starts from
  • RFC 8693 · RFC 9396 token exchange and rich authorisation requests, the delegation the policy sees
  • Envoy ext_authz the gRPC attachment for Envoy, Istio and agentgateway
  • PingAccess Add-on SDK the rule, and the console form it draws
who is behind it

Built and run in the open by ID Partners

ID Partners is an Australian digital identity consultancy. We built this, we run it in the open, and we work with the Ping products underneath it every week. We also contribute to the OpenID AuthZEN working group, where the profile that authorises tool calls is being written. The engagement fits the stage you are at.

assess

Architecture and policy workshop

  • Your agentic use cases mapped to enforcement points, decision points and the tokens between them
  • Which gateway carries which traffic, and where a service must enforce for itself
  • The delegation model: token exchange, the actor claim, step-up and consent, identity proofing
pilot

One use case, end to end, in your environment

  • The enforcement point on your gateway, or the SDK in your service, in non-production
  • Your API's metadata published, discovery on, the PDP reached through the AuthZEN adapter
  • An agent that resolves the challenges, demonstrated to your stakeholders
run

Production hardening and support

  • Allowlists, DPoP, user-token verification, federation trust anchors, fail mode per layer
  • Rollout across applications and the other gateways in the estate, one contract throughout
  • A named engineer, and the fix in the open-source line rather than a fork you carry