IDPartnersidp-auth-peps · reference
gateways/pingaccess/ · Java

The PingAccess rule

AuthZEN PDP is an Add-on SDK rule: the PingAccess counterpart of the Kong plugin, sending the PDP the same AuthZEN request and rendering the same challenges, so a client cannot tell from a deny which gateway said no. Token and actor-claim extraction, RFC 9470 challenges, REST mapping, RFC 9728 and AuthZEN discovery and policy layers are native Java; DPoP verification and every decision on an MCP route are delegated to coaz-pep, as Kong does. It is configured in the PingAccess console or through the admin API, spelled the same as every other PEP here.

Not to be confused with PingAccess's built-in PingAuthorize Policy Decision Access Control and PingAuthorize Access Control rules. Those speak PingAuthorize's own APIs to PingAuthorize alone. This one speaks AuthZEN 1.0 to any conformant PDP, discovers which PDP decides for a resource, and returns resolvable challenges.

Upgrading to 0.4.0

0.4.0 refuses things 0.3 let through. A rule saved under 0.3 may fail to configure under 0.4.0, and every request through it is then a 500 deny until it is fixed, so check each rule before deploying the jar.

Build and install

The Add-on SDK is on no public Maven repository. Take it from a PingAccess install or, without one, from the public Docker image, which needs no licence to pull.

cd gateways/pingaccess/authzen-pdp
id=$(docker create pingidentity/pingaccess:9.1.0-latest)
mkdir -p .pa/lib && docker cp "$id:/opt/server/lib/pingaccess-sdk-9.1.0.1.jar" .pa/lib/ && docker rm "$id"
mvn verify                                    # tests, the coverage ratchet, the jar
# or, against an install:
mvn -Dpa.server.root=/opt/pingaccess -Dpa.sdk.version=9.1.0.1 verify

The jar is target/authzen-pdp-pingaccess-<version>.jar; PingAccess supplies Jackson, jose4j and slf4j, so nothing is bundled. The bytecode is Java 17, so it needs a Java 17 or later runtime; it has been tested on PingAccess 9.1.0.1 (Java 21) and nothing earlier. Copy it to <PA_HOME>/deploy/ and restart; the DevOps image's local server profile is /opt/in/instance/deploy/. The rule then appears as AuthZEN PDP (class com.idpartners.pa.authzen.AuthZenRule) for Site and Agent destinations.

The rule in the console

The console draws one form from the rule's descriptor: each field with a widget, a label, a default, a help note, and whether it is required or behind Show Advanced Settings. The same descriptor is what the admin API serves at GET /pa-admin-api/v3/rules/descriptors/AuthZenPdpRule. Captured from the demo's PingAccess (docker compose --profile pingaccess) as configured by its start-up hook.

The PingAccess console's Access > Rules page listing the authzen-pdp rule of type AuthZEN PDP
Access → Rules. The rule is in the Access Control category, so it runs before any processing rule on the same policy.
The rule expanded in the list: its Properties tab showing every set value with the configuration key in brackets
The saved rule. Expanding the row shows its properties, each label with the configuration key in brackets, plus the applications and resources it is attached to. The pencil opens the form.
The AuthZEN PDP rule's configuration form in the PingAccess console
The rule's form. The basic fields: the PDP, the route's style and requirements, discovery and layers. Every widget is a standard console control: text, concealed (secrets are stored encrypted and never returned in plain text), checkbox, select, or a multi-value list.
The same form with Show Advanced Settings expanded
Show Advanced Settings. Nothing behind it is needed for a first rule; everything behind it is a tuning, a safety valve or a development-only switch.
An application's API Policy tab in the PingAccess console with the rule attached
Applications → the application's API Policy. Where the rule is attached. Drag it towards the top, so a deny costs nothing further.

Every field

Names are as saved under configuration; the console shows a label with the name in brackets. advanced marks a field behind Show Advanced Settings.

The PDP

FieldWidget · defaultMeaning
authzen_url requiredtextThe static PDP: decides for a resource that publishes nothing, and is always permitted.
authzen_api_keyconcealedBearer key for authzen_url, sent over https only. A discovered PDP never receives it. Stored encrypted; the API returns {"encryptedValue": "OBF:JWE:…"}.
pdp_timeout_ms advancedtext · 10000Deadline on each PDP call, the whole exchange included.
pdp_ssl_verify advancedcheckbox · trueCertificate verification on every outbound call. Off trusts any chain but still checks the host name, and needs allow_insecure: dev only.

The route

FieldWidget · defaultMeaning
pep_labeltext · pingaccess-pepNames this PEP in challenges and the X-PDP-PEP header.
styleselect · restrest (resource server) or mcp (MCP edge: every request goes to coaz-pep, and coaz_url is required).
require_tokencheckbox · trueDeny without a readable access token. A token PingAccess did not validate is a 401 either way, unless allow_insecure.
require_dpopcheckbox · falseDelegate the RFC 9449 sender-constraint check to coaz-pep; needs coaz_url, and is refused at configuration time without it.
require_user_logincheckbox · falseDeny without a verified X-User-Token, with a login challenge. Needs user_token_jwks_url.
stepup_scopetextScope named in a step-up challenge when the PDP's advice names none.
resourcetextThe protected resource's identifier (RFC 8707), the key discovery starts from.
forward_access_tokencheckbox · falseSend the raw token to the PDP as context.access_token; refused over plain http.

coaz-pep

FieldWidget · defaultMeaning
coaz_urltextcoaz-pep's HTTP check API. Required for style: mcp and for require_dpop.
coaz_api_keyconcealedIts CHECK_API_TOKEN; required with coaz_url, and sent over https only.
mcp_upstream_urltextWhere tools/list lives; coaz-pep reads each tool's mapping from it.
federation_entity_urltextRelay the resource's two well-known documents from coaz-pep at this URL.
coaz_defaults advancedcheckbox · trueGovern every MCP method by the COAZ-MCP binding's default table; false keeps the old pass-through for methods other than tools/call.
coaz_timeout_ms advancedtext · 15000Deadline on each coaz-pep call, the whole exchange included.

Discovery

FieldWidget · defaultMeaning
pdp_discoveryselect · offoff, authzen or resource. No federation mode; the same reason as Kong, and the same remedy: behind coaz-pep.
pdp_metadata_ttl advancedtext · 300Cache TTL, seconds, for resource and PDP metadata. A fetch that fails, or a document that does not validate, serves the last good copy for up to one more TTL; after that, or with none, the layer is unavailable and its failure mode decides.
pdp_allowlistlistPermitted discovered-PDP prefixes; required when pdp_discovery is on. authzen_url is always permitted.
resource_metadata_allowlistlist · anyPermitted resource prefixes for metadata fetches.
pdp_discovery_insecure advancedcheckbox · falseAllow http for discovered URLs; needs allow_insecure (dev only).

Layers

FieldWidget · defaultMeaning
pdp_layerslist · ["resource"]Ordered PDPs, every one of which must permit; fail-open / fail-closed per entry. An empty list means resource.
fail_modeselect · closedWhat a layer does when its PDP is down (no connection, a timeout, a 5xx, a 429), unless it says for itself. A refusal - a 4xx, a redirect, an answer that is not a decision - never opens.

The user token

FieldWidget · defaultMeaning
user_token_jwks_urltextVerify X-User-Token against this JWKS, over https (jose4j is on PingAccess's classpath); exp and sub are required. Unset, the token is ignored, or decoded under allow_insecure.
user_token_issuer advancedtextExpected iss when verifying.
user_token_audiencetextExpected aud; required with the JWKS.
user_token_subject advancedselect · principalWhose login counts. principal: only a token whose sub is the access token's subject. pdp: another person's verified login too - a staff member approving for a customer - and the PDP, told user_sub and user_iss, decides. Either way, not the access token itself and not a delegated (act) token.

Transport and compatibility

FieldWidget · defaultMeaning
legacy_subject_identity advancedcheckbox · trueAlso send the non-standard subject.identity beside subject.id.
allow_insecure advancedcheckbox · falseDevelopment only: start with what the rule otherwise refuses (unvalidated access tokens, an unverified X-User-Token, keys over http, no pdp_allowlist, TLS verification off), each relaxation logged at configure time.

Saving validates twice: field constraints first, reported on the field under a Save Failed banner (the PDP URL must not be blank, the drop-downs must hold one of their options, the numbers must be numbers), then the rule's own configure. That refuses a URL the rule could not call (not absolute http or https, no host, user info), style: mcp or require_dpop without coaz_url, a JSON null for a drop-down, and - unless allow_insecure is ticked - every missing or weakened security setting, listed in one banner. The descriptor sets agentCachingDisabled: true: an agent never caches this rule's verdict, because it depends on the token, the body and the PDP's current view.

The admin API

The same configuration as JSON, keyed by the names above. This is the repository's reference sample; demo/pingaccess/hooks/81-after-start-process.sh is the whole sequence, site and application included, as a script.

curl -sk -u administrator:$PA_PASSWORD -H 'X-Xsrf-Header: PingAccess' -H 'Content-Type: application/json' \
  https://pa-admin:9000/pa-admin-api/v3/rules -d '{
  "name": "authzen-pdp",
  "className": "com.idpartners.pa.authzen.AuthZenRule",
  "supportedDestinations": ["Site", "Agent"],
  "configuration": {
    "authzen_url": "https://pdp.bank.example",
    "authzen_api_key": "...",
    "pep_label": "PEP#2 (Bank API edge)",
    "style": "rest",
    "require_token": true,
    "pdp_discovery": "resource",
    "resource": "https://api.bank.example",
    "resource_metadata_allowlist": ["https://api.bank.example"],
    "pdp_allowlist": ["https://pdp.bank.example"],
    "pdp_layers": ["https://estate-pdp.example fail-open", "resource"],
    "user_token_jwks_url": "https://as.bank.example/jwks",
    "user_token_audience": "https://api.bank.example"
  }
}'

A saved rule reads back as GET /pa-admin-api/v3/rules/{id} with the form's values under configuration; concealed fields come back encrypted.

Attaching it to an application

Then POST /applications with the rule in the API policy. The demo's hook creates the site, the virtual host and the application in that order and attaches the rule; this is the shape of the application.

{
  "name": "bank",
  "contextRoot": "/bank",
  "destination": "Site",
  "siteId": 1,
  "virtualHostIds": [1],
  "accessValidatorId": 0,                     # unprotected: the demo's tokens are unsigned, so its rule sets allow_insecure
  "enabled": true,
  "policy": { "API": [ { "type": "Rule", "id": 1 } ] }
}

In the console: Applications → Applications, edit the application, the API Policy tab, drag authzen-pdp from Available Rules onto the policy. Applications are created disabled unless enabled is set. Unprotected applications still run rules, but the rule refuses a token PingAccess did not validate unless it is configured with allow_insecure, which is how the demo uses unsigned tokens. A production application has an access token validator, and then PingAccess validates the bearer before the rule runs.

What PingAccess does, and what the rule does