PingAuthorize Talk to us

HTTP servlet extension · built against Server SDK 10.1 and 11.1

AuthZEN Adapter

Serves the OpenID AuthZEN Authorization API 1.0 from inside PingAuthorize and turns each request into a native decision or query. Any AuthZEN client - an API gateway, an application, an AI agent - can rely on the policies you already govern, with no proxy between the API and the decision.

The shape of it

Supported AuthZEN operations

An AuthZEN evaluation {subject, action, resource, context} becomes a governance-engine decision request. Each tuple element travels as a JSON string under a configurable attribute prefix, so a Trust Framework request resolver can read it; the engine's verdict maps straight back to the AuthZEN boolean, and any policy reason rides along in context.

The subject id is accepted under id (spec) or identity (as earlier Ping enforcement points send it) - identity wins when both are present. The adapter reads {"authorised":…}, {"authorized":…} or {"decision":"PERMIT"|"DENY"} from the engine, so it works against either dialect.

Flat attributes, Shared Signals events and the SPIFFE caller check are off unless configured; advice mapping applies whenever policy returns the advice statement codes.

AdditionTurned on byWhat it does
Flat tuple attributesflat-tuple=trueAlso sends the tuple's scalars as dot-free attributes, for policies that bind request attributes directly.
Advice mappingalways on, policy-drivenFolds step-up-required and identity-proofing-required statements into the response context.
SSF Transmitterssf-shared-secretQueues a Security Event Token for each evaluation, and serves stream management for one push receiver.
SPIFFE calleraccepted-spiffe-idTakes the caller from the mTLS peer's verified certificate and forwards it to policy as <prefix>.caller.

API surface

Eight paths, one extension

MethodPathPurposeGate
POST/access/v1/evaluationSingle access evaluation - returns a decision object, not an array.caller
POST/access/v1/evaluationsBatch evaluation. Honours options.evaluations_semantic: execute_all (default), deny_on_first_deny, permit_on_first_permit. Each entry overlays the request-level defaults; an empty batch evaluates the top-level tuple once.caller
POST/access/v1/search/subjectSubject search via the governance-engine query endpoint.caller
POST/access/v1/search/resourceResource search via policy: one evaluation whose PERMIT carries an entitled_accounts statement, else the empty set.caller
POST/access/v1/search/actionAction search via the query endpoint.caller
GET/.well-known/authzen-configurationMetadata document with absolute endpoint URLs and eight vendor-namespaced capability URNs. Honours X-Forwarded-Proto/Host/Port behind a TLS-terminating edge.public
GET/.well-known/ssf-configurationSSF Transmitter metadata. 404 unless SSF is enabled.public
POSTGETDELETE/ssf/streamSSF stream management - create, read, delete. 404 unless SSF is enabled. The read returns the receiver's authorization_header: protect it with api-key or a SPIFFE caller check, because with neither set the adapter admits any caller.caller

Metadata document

GET /.well-known/authzen-configuration
{
  "policy_decision_point": "https://pdp.example",
  "access_evaluation_endpoint": "https://pdp.example/access/v1/evaluation",
  "access_evaluations_endpoint": "https://pdp.example/access/v1/evaluations",
  "search_subject_endpoint": "https://pdp.example/access/v1/search/subject",
  "search_resource_endpoint": "https://pdp.example/access/v1/search/resource",
  "search_action_endpoint": "https://pdp.example/access/v1/search/action",
  "capabilities": [
    "urn:idpartners:authzen:capability:access-evaluation",
    "urn:idpartners:authzen:capability:access-evaluations",
    "urn:idpartners:authzen:capability:search-subject",
    "urn:idpartners:authzen:capability:search-resource",
    "urn:idpartners:authzen:capability:search-action",
    "urn:idpartners:authzen:capability:evaluations-semantic:execute-all",
    "urn:idpartners:authzen:capability:evaluations-semantic:deny-on-first-deny",
    "urn:idpartners:authzen:capability:evaluations-semantic:permit-on-first-permit"
  ]
}

Errors, one shape

Every failure is {"error", "error_description"}. A missing field or bad JSON is 400 invalid_request; a refused caller is 401 unauthorized; an unknown or disabled path is 404 not_found; an engine that cannot be reached or answers without a decision is 502 pdp_error - and the engine's own text goes to the server error log, not to the client.

AuthZEN 1.0 defines capabilities as an optional array of PDP-specific URNs. No standard URNs are registered yet, so the adapter publishes vendor-namespaced ones that truthfully describe its surface, including the three batch semantics.

Translation bench · live

AuthZEN in, PingAuthorize decision out

The bench mirrors the adapter's mapping rules in your browser: edit the request, flip the arguments, and watch what PingAuthorize's decision engine would be asked. Then pick how the engine answers and see the AuthZEN response the PEP receives. Highlighted keys are what the adapter adds beyond the spec's tuple.

Governance-engine requestPOST pdp-url

        
AuthZEN response200

        
added by the adapter, not in the AuthZEN tupleTuple elements are JSON strings - a Trust Framework attribute name cannot contain a dot, which is what flat-tuple is for.

Reading the decision

Advice a PEP can branch on

Two policy statement codes are mapped into the AuthZEN response context as custom advice fields, so a PEP can trigger step-up authentication or identity proofing rather than act on a bare DENY. These fields are ID Partners conventions, not part of AuthZEN, and the PEP decides how to issue any challenge.

Statements are scanned in order and the last advice statement's message wins. A missing or malformed payload still sets the bare flag; it just contributes no detail. With no advice at all, the reason comes from the engine's reason field, else the first statement's payload.

Statement codeResponse context
step-up-requiredstep_up_required: true
step_up_scope ← payload scope
reason ← payload message
identity-proofing-requiredidentity_proofing_required: true
identity_proofing_doctype ← payload doctype
reason ← payload message
anything elsereason ← engine reason, else first statement payload

Batch semantics

Where a batch stops

The Access Evaluations endpoint evaluates entries in order and short-circuits per options.evaluations_semantic. The response omits a top-level decision and returns the evaluations array for the entries it reached. Below, the same three-entry batch - PERMIT, DENY, PERMIT - under each semantic.

evaluations_semanticStops atEntry 1Entry 2Entry 3Results returned
execute_all (default)nevertruefalsetrue3
deny_on_first_denythe first falsetruefalsenot evaluated2
permit_on_first_permitthe first truetruenot evaluatednot evaluated1

Each entry overlays the request-level subject, action, resource and context - the entry wins where present. A batch envelope with no entries evaluates its top-level tuple once. Every entry that is evaluated publishes its own SSF event.

Conformance testing · OpenID Foundation

Run through the OpenID Foundation's conformance suite

The adapter is tested with the OpenID Foundation conformance suite, the suite behind OpenID Connect and FAPI certification. In September 2026 all six of its AuthZEN policy decision point plans were run, on suite release 5.3.1, against PingAuthorize 11.1 holding the certification scenario's fixture policy, built as code, with the metadata document found by discovery: 147 of 147 tests passed. The run is held in ID Partners' account on the suite; ask us for the plan logs.

Two things to know before quoting that number. The suite's AuthZEN plans are alpha tests, not yet part of the OpenID certification programme, so this is evidence of conformance rather than a certificate, and ID Partners does not describe the adapter as certified. And one module still tests a rule from the draft of the certification scenario - that a batch entry inherits a missing type from the request-level default - which the published scenario and AuthZEN 1.0 reversed. The adapter follows the published rule; the batch-defaults=fill setting lets it satisfy the suite as it stands, and comes off when the suite catches up.

PlanSuite plan idResult
Access evaluationSqMxLMEydMHzZpassed
Access evaluations, batchUPYVDDIVYMlE2passed
Access evaluations, comprehensiveR1qiA8KCOn97jpassed
Subject searchk6qP6j6Hcv2Rypassed
Resource searchtfedjdGtk3w7tpassed
Action searchfc5HiJso5fzBWpassed

Suite release 5.3.1, September 2026, with pdp_auth_type=none and pdp_server_metadata=discovery: the run that exercises the metadata checks. The AuthZEN Authorization API 1.0 is an OpenID Foundation specification.

Who may call · live

Three ways in, tried in order

api-key says that someone holding the shared key called. On a connection handler that requires a client certificate, the handler has already established which workload called before the adapter runs - and the adapter reads the peer's SPIFFE ID off that verified chain.

  1. The peer's SPIFFE ID is listed in accepted-spiffe-id. Admitted, no bearer needed, and forwarded to policy as <prefix>.caller (plus callerSpiffeId with flat-tuple).
  2. Otherwise, unless spiffe-required is true, a valid Authorization: Bearer <api-key>. Admitted with no caller attribute - its absence is how policy tells a key holder from a listed workload.
  3. Otherwise, when no api-key is configured either, anyone.

The two /.well-known/ documents skip the gate by design. The caller rides on evaluations, batches and resource search; the query path carries none. spiffe-required with no listed ID, or an ID that is not a well-formed SPIFFE ID, is refused when the adapter is configured, rather than silently locking everyone out.

Adapter configuration

Shared Signals · opt in

Evaluation events for a Shared Signals receiver

Set ssf-shared-secret and the adapter acts as a Shared Signals transmitter for the evaluations it answers - single and batch. Searches, and calls made directly to PingAuthorize's own decision APIs, produce no events. The adapter queues evaluation events for background delivery. Delivery is best effort. Unset, the SSF paths answer 404 and nothing is published.

# register the Receiver
curl -s https://pdp.example/ssf/stream \
  -H "Authorization: Bearer <api-key>" \
  -H 'Content-Type: application/json' \
  -d '{ "aud": "receiver-app",
        "events_requested": ["https://schemas.idpartners.com.au/ssf/authzen-decision"],
        "delivery": { "method": "urn:ietf:rfc:8935",
                      "endpoint_url": "https://receiver.example/events",
                      "authorization_header": "Bearer <receiver-secret>" } }'
# 201 {"stream_id":"…","iss":"…","aud":"receiver-app","events_supported":[…],
#      "events_requested":[…],"events_delivered":[…],"delivery":{…}}

GET /ssf/stream?stream_id=… reads it back; DELETE unregisters it, idempotently. events_delivered is the intersection of what was asked for and what the Transmitter supports - an unrecognised event type is simply not delivered, not an error.

POST https://receiver.example/events
Content-Type: application/secevent+jwt

# header
{ "alg": "HS256", "typ": "secevent+jwt" }
# claims - no sub or exp
{
  "iss": "https://pdp.example",
  "jti": "0d9c6b1e-4f7a-4a52-9a0e-3c1b7d2e8f41",
  "iat": 1789180200,
  "aud": "receiver-app",
  "events": {
    "https://schemas.idpartners.com.au/ssf/authzen-decision": {
      "subject": "agent-42",
      "action": "transfer",
      "resource": "acct-1",
      "decision": "DENY",
      "step_up_required": true,
      "step_up_scope": "payments:high-value",
      "reason": "Transfers over 10,000 AUD need a stronger sign-in",
      "attrs": { "amount": 25000, "currency": "AUD", "to_account": "SAV-2002" }
    }
  }
}

Advice fields from the decision context are merged in at the top level. attrs carries a fixed whitelist of request-context keys - amount, currency, to_account, user_scope, channel - and is omitted when none are present.

How delivery behaves, and current limits

  • Never in the decision's way. Events are delivered on a background thread with 5-second timeouts, so a slow or unreachable Receiver cannot add latency to, or fail, the decision that triggered them.
  • Best effort. Up to 256 events queue for delivery; beyond that, new events are dropped and counted. A failed delivery is not retried.
  • Push to one Receiver. RFC 8935 push delivery to a single registered stream, managed with create, read and delete on /ssf/stream. The registration lives in memory, so a Receiver registers again after a server restart.
  • Shared-secret signing. Tokens are signed HS256 with ssf-shared-secret, shared with the receiver out of band. No signing keys are published.
  • Discovery metadata is partial. /.well-known/ssf-configuration returns issuer, delivery_methods_supported and configuration_endpoint. It omits jwks_uri, which SSF 1.0 requires when the transmitter signs tokens, and spec_version, so receivers will assume 1_0-ID1. Check your receiver accepts this before relying on it.

Configuration

Sixteen extension arguments

Each is an extension-argument:name=value on the HTTP servlet extension.

ArgumentDefaultWhat it does
Reaching the governance engine
pdp-url requiredhttps://localhost:8443/governance-engineThe JSON PDP decision endpoint.
query-url…/governance-engine/queryQuery endpoint used by subject and action search.
pdp-secret-headerCLIENT-TOKENHeader carrying the shared secret.
pdp-secretunsetThe shared secret value.
trust-any-server-certtrueTrust any TLS certificate from the engine. Suits a loopback engine; set false when the engine is reached over a network.
timeout-millis12000Connect and read timeout for engine calls.
Shaping the decision request
domain-prefixemptyThe decision request's domain.
pdp-serviceemptyThe decision request's service.
pdp-actionemptyThe decision request's action.
attribute-prefixemptyTrust Framework attribute prefix, e.g. Gateway. Empty names the attributes subject, action, resource, context.
flat-tuplefalseAlso send agentId, actionName, resourceType, resourceId (and callerSpiffeId) as dot-free attributes.
Who may call
api-keyunsetRequire Authorization: Bearer <api-key> on every endpoint except the two /.well-known/ documents.
accepted-spiffe-idunset · repeatableSPIFFE IDs admitted from the mTLS peer's verified certificate, without the key, and forwarded as <prefix>.caller.
spiffe-requiredfalseRefuse anything but a listed SPIFFE ID, dropping the key fallback. Needs at least one accepted-spiffe-id.
Shared Signals
ssf-issuerhttps://localhost:1443The iss every Security Event Token is signed as.
ssf-shared-secretunsetHS256 signing secret. Unset disables the whole SSF surface.

Install and compatibility

Two commands into a running server

The adapter ships as a manage-extension bundle for your PingAuthorize major version. Install it, then apply the registration batch inside it - which also adds the adapter to the HTTPS connection handler.

# 1. install the bundle
<paz>/bin/manage-extension --install authzen-adapter-<version>.zip

# 2. register and serve it - set the arguments in the batch first
<paz>/bin/dsconfig --no-prompt --batch-file config/register.dsconfig

# serve it from another handler too, e.g. the SPIFFE mTLS handler
<paz>/bin/dsconfig set-connection-handler-prop \
  --handler-name "SPIFFE mTLS Connection Handler" \
  --add "http-servlet-extension:AuthZEN Adapter"
PingAuthorizeServlet APIBuilt against Server SDK
Version 10javax.servlet 4.010.1.0.0
Version 11jakarta.servlet 6.111.1.0.0
  • One code base, both majors. PingAuthorize 11 moved its Server SDK from javax.servlet to jakarta.servlet and refuses a bundle built for the other. Both bundles are built from the same source. Talk to ID Partners to confirm support for your exact PingAuthorize release.
  • Nothing to conflict with. The bundle carries only the extension jar; JSON, servlet and SDK classes come from the running server.
  • Coverage-gated. The build fails unless the methods that admit a caller, shape a decision request, read an answer, stop a batch or build an event have full line and branch test coverage.