authz-details-rs: RFC 9396 Authorization Details
Your bank’s payment API has adopted OAuth 2.0 Rich Authorization Requests (RFC 9396). Instead of a flat list of scopes, clients now express what they want to do with structured JSON:
{
"type": "payment_initiation",
"actions": ["initiate", "status"],
"instructedAmount": {
"currency": "EUR",
"amount": "123.50"
},
"creditorName": "Merchant A",
"creditorAccount": {
"iban": "DE02100100109307118603"
}
}
Your authorization server must now answer a new kind of question: is this a well-formed request for a type the server understands? Did the client re-ask for more than it was granted at the authorization endpoint? May a delegated service act on the user’s behalf, within the intersection of both parties’ permissions?
authz-details-rs provides idiomatic Rust types and evaluation functions for
RFC 9396 authorization_details: parsing, JSON Schema validation, named
rules with JSONPath field conditions and time windows, subset comparison
with explanations, and delegation. It is storage-agnostic, designed for use
by both authorization servers and resource servers, and shaped like the
other BAC crates: rules are built with builders, policies expose evaluate,
evaluate_at and check_access, time is an acls-rs Timestamp, and
policies compose with acls-rs RBAC and (optionally) hbac-rs HBAC.
Adding authz-details-rs to your project
[dependencies]
authz-details-rs = "0.2"
Optional features:
| Feature | Enables |
|---|---|
regex-matchers | The ComparisonOp::Matches regular-expression operator |
hbac | authz_details_rs::hbac::HostScopedPolicy: HBAC host/service access composed with rules |
[dependencies]
authz-details-rs = { version = "0.2", features = ["hbac"] }
Basic usage
Register the types you understand, parse the authorization_details
request parameter, validate it, and enforce a rule:
#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;
use serde_json::json;
let registry = TypeRegistry::new()
.with_type(TypeSchema::new(
"payment_initiation",
json!({
"type": "object",
"required": ["actions", "instructedAmount"],
"properties": {
"actions": {"type": "array"},
"instructedAmount": {
"type": "object",
"properties": {
"amount": {"type": "string"},
"currency": {"type": "string"}
}
}
}
}),
"Payment initiation authorization",
))
.unwrap();
let details = AuthorizationDetails::from_json(
r#"[{"type": "payment_initiation",
"actions": ["initiate"],
"instructedAmount": {"currency": "EUR", "amount": "100.00"}}]"#,
)
.unwrap();
registry.validate(&details).unwrap();
let mut policy = AuthzDetailsPolicy::new();
policy
.add_rule(
AuthzDetailsRule::builder("eur-up-to-1000")
.type_id("payment_initiation")
.field("$.instructedAmount.amount").le(1000)
.field("$.instructedAmount.currency").eq("EUR")
.build()
.unwrap(),
)
.unwrap();
let result = policy.evaluate(&details);
assert!(result.is_allowed());
assert_eq!(result.matched_rules, ["eur-up-to-1000"]);
}
Core types
| Type | Description |
|---|---|
AuthorizationDetail | One authorization_details entry: a type_id plus a JSON object of members, with accessors for the RFC 9396 common fields (actions(), locations(), datatypes(), identifier(), privileges()) |
AuthorizationDetails | The whole request parameter (from_json / to_json); dereferences to a slice |
TypeSchema | A type: a JSON Schema (Draft 2020-12), an optional ComparatorConfig, and a description |
TypeRegistry | Registered types with compiled schemas: validate, is_subset, explain_subset, intersect |
ComparatorConfig | Field-level rules (numeric, array) for typed subset comparison and delegation |
AuthzDetailsRule | A named rule for one type: Conditions (field matchers, time windows) combined with a MatchMode |
TemporalAuthzDetailsRule | A rule with a valid_from / valid_until window, like TemporalHbacRule |
AuthzDetailsPolicy | Named rules with add_rule / remove_rule / enable_rule / disable_rule and evaluate / evaluate_at |
AuthzDetailsEvaluationResult | allowed, a Verdict (Allow / Deny / NoRule), matched_rules, not_matched_rules, warnings |
ComposedPolicy | A policy composed with an acls-rs RbacPolicy |
The evaluation workflow
| Step | API | Where it runs |
|---|---|---|
| Parse | AuthorizationDetails::from_json | Authorization server: read the request parameter |
| Validate | TypeRegistry::validate | Authorization server: accept only well-formed details of registered types |
| Enforce | AuthzDetailsPolicy::evaluate_at, ComposedPolicy | Authorization or resource server: named rules, optionally composed with RBAC/HBAC |
| Narrow | TypeRegistry::explain_subset | Token endpoint: a re-request must be covered by the grant; rejections name the offending field |
| Delegate | TypeRegistry::intersect | Delegated flows: the effective grant is the intersection of subject and actor grants |
Malformed input fails with its own error type – ValidationError,
RuleError, PolicyError, TemporalError, SubsetError,
DelegationError, all convertible into AuthzDetailsError. Policy
outcomes are values, not errors: a denied request, an uncovered re-request
and a disjoint delegation are reported through results, reports and empty
lists.
Agentic workflows
AI agents acting on behalf of a user map directly onto the four steps. The
user’s consent is validated and becomes the subject grant; the agent’s
registered capabilities are the actor grant; compute_intersection yields
what the agent may do for this user, and each hop to a sub-agent can only
narrow it further. Every tool call then requests a task-scoped token that
must pass is_subset, with locations binding the token to one resource
server, and a rejection names the field that exceeded the grant. Finally the
resource server evaluates its own rules composed with RBAC for the agent role
(see the acls-rs bridge) and, on Linux hosts,
with HBAC host access (see HBAC Integration), which
catches prompt-injected requests that the agent itself did not stop.
Two runnable examples walk through the whole flow, in both Rust
(crates/authz-details-rs/examples/) and Python
(crates/authz-details-python/examples/):
| Example | Scenario |
|---|---|
agent_linux_workstation | A coding agent on a Linux host: linux_file, linux_exec and systemd_unit details; denies ~/.ssh reads, sudo, other hosts, paths smuggled behind allowed ones, and work outside working hours |
agent_web_delegation | A travel agent and a payment sub-agent: RFC 9396 payments, calendar and email; denies over-limit amounts, other creditors, token replay at another bank, expired mandates, and exfiltration by email |
cargo run -p authz-details-rs --features hbac --example agent_linux_workstation
cargo run -p authz-details-rs --example agent_web_delegation
What to read next
- Schema Validation – registering types and validating details against JSON Schemas.
- Field Matching – JSONPath conditions and comparison operators.
- Rules and Policies – named rules, the rule builder, and policy evaluation.
- Temporal Rules – validity windows and recurring time windows.
- Subset Comparison – enforcing that a re-request only narrows the original grant.
- Delegation – intersecting subject and actor grants for multi-party authorization.
- acls-rs Bridge – composing rules with RBAC.
- HBAC Integration – composing rules with HBAC host/service access.