Repository navigation
ARAP: the inline denial-binding form and the PEP's echo #660
Description
Problem
The inline denial-binding form is never specified. A self-contained binding_token must carry the denied Subject, Resource, Action, and authorization-relevant Context "inline or as a binding_hash", and the inline form is RECOMMENDED for interoperability because it is compared structurally and needs no agreed byte canonicalization. binding_hash, binding_context_members, and denial_expires_at are named. The inline claims are not. The hashed form is fully specified, so two independent implementations can interoperate today by using it; what is blocked is the portable use of the form the text recommends.
The claim list's stated force does not match the verification procedure. The list is introduced by "SHOULD include the following claims", yet aud inside it is REQUIRED and carries a verifier-side MUST-reject, the verification steps require the expiry, and both key selection and issuer trust depend on iss, which carries no keyword at all. The interoperable JWT contract is stricter than its own introduction says.
The PEP's echo does not compose with the service's requirement. denial.binding_token is REQUIRED when denial.evaluation_id is absent and OPTIONAL otherwise. A PEP holding both may therefore omit the token and remain conformant, while an Independent Access Request Service requires it and rejects the submission. Two conformant implementations fail to interoperate.
Recommendation
Name the inline binding claims, so the recommended form is usable across implementations.
Define a normative JWT subset and keep the general statement that binding_token is opaque and format-flexible:
- REQUIRED:
iss,aud,exp, the binding representation (inline claims orbinding_hash), andbinding_context_memberswhen Context participates in the binding. - RECOMMENDED:
iat,jti. - OPTIONAL:
evaluation_id, and deployment-specific claims.
Make the PEP's echo unconditional: the PEP echoes every denial member the PDP supplied, unchanged. This changes the force of an existing rule, which is why it is a working-group decision rather than an editorial correction.
Related
Once the claims are named, the illustrative tokens need correcting. Every binding_token in the base draft and its companions decodes to correlation data alone, with no aud, whose absence a conformant service must reject, and no binding_context_members. The base draft's token carries an evaluation_id only, which the text says does not satisfy the independent-service requirement it is meant to illustrate. That is an editor task, not a decision for this issue.
Activity
vatsalgupta commentedon Oct 2, 2026
On naming the inline claims, one option from building the example tokens: a single binding claim whose value is exactly the object that binding_hash hashes today.
"binding": {
"subject": {...}, (with properties.act removed)
"resource": {...},
"action": {...},
"context": {...} (only the binding_context_members)
}
Bulk uses items in place of resource and action, as the bulk hash does.
Then inline and hashed are the same object. The inline form is compared structurally, and the hashed form is the hash of the same thing. That means one construction to define instead of two, and no second set of claim names to keep in sync.
+1 on making the echo unconditional. A PEP should not have to know whether the service it talks to is independent.
mcguinness commentedon Oct 13, 2026
Two additions from reviewing #690 against this issue. The approval-side claim set belongs to #659, so only one observation for there: the family's two
approval.stateexamples both sit in deployments where the PDP can resolveapproval.idfrom trusted state, so nothing in the examples demonstrates the independent-PDP topology that member exists for.Removing
subject.properties.acthas no defined result whenactis the only member. Hash Construction says<Subject>is "the bound Subject withsubject.properties.actremoved". Structural Comparison says "everypropertiesmember present in the bound values included, except thatsubject.properties.actis excluded". Neither says whether an emptiedpropertiesis kept or omitted, and the two readings produce different digests. From the Callback example's initial evaluation, whose Subject carriesactas the solepropertiesmember:Omitting is the reading that works, because the exclusion exists so that a PEP which normalizes the actor to
client.actorstill compares equal. Keeping{}defeats that: the bound Subject normalizes to{type, id, properties: {}}while the submission carries{type, id}, and a conforming Access Request Service rejects withinvalid_denial_binding. Proposed rule: after removingsubject.properties.act, ifpropertiesis then empty, omit it.This is not only a hashed-form problem. Structural comparison requires objects to have "the same set of member names", so
{properties: {}}and an absentpropertiesare unequal inline as well. The inline form avoids agreeing on byte canonicalization; it still needs an agreed normalization.On the single
bindingclaim proposed above: it holds up, and one construction instead of two is the right trade. It inherits the normalization question, so whichever way that is settled should be stated once and apply to both forms.Require issuance support for a claimed binding capability