Loading WASM…
AuthZEN — The Missing Standard for Authorization Decisions

OpenID AuthZEN Authorization API  ·  draft-gazitt-oauth-authzen-issuance  ·  draft-gazitt-oauth-authzen-token-exchange

Every authorization system makes a binary decision: allow or deny. OAuth 2.0, OpenID Connect, and RFC 8693 Token Exchange all say the decision is "governed by local policy" — without defining what that policy looks like or how it's evaluated. AuthZEN fills exactly that gap: a standard API for externalising the authorization decision to any Policy Decision Point.

Policy Enforcement Point (PEP) Envoy / App / AS POST /access/v1/evaluation {subject, resource, action} AuthZEN API Standard Interface /access/v1/evaluation /access/v1/evaluations evaluate(five-tuple) In-Memory PDP OpenFGA OPA / Rego {decision: true/false}
The Five-Tuple: AuthZEN's Mandatory Inputs

Every AuthZEN evaluation request carries five fields. The PDP evaluates these and returns a single boolean decision.

Subject
type + id

Who is requesting? A user, a workload, an AI agent SPIFFE ID.

e.g. type: "workload"
id: "spiffe://..."
Resource
type + id

What is being accessed? A document, a token audience, a tool endpoint.

e.g. type: "token-audience"
id: "https://api.b.example"
Action
name

What operation? Read, write, invoke, exchange. Maps to an OpenFGA relation or OPA rule.

e.g. name: "can_read"
name: "exchange"
Context
Optional properties

Environmental data: time, IP, risk score, tenant ID. Fed into the PDP for attribute-based rules.

e.g. {ip: "10.0.0.1",
time: "09:00 UTC"}
Decision
{ "decision": bool }

The PDP's single binary answer. Simple, consistent, swappable — any PDP backend returns the same shape.

true = allow
false = deny
Why this matters: Every OAuth spec, every token exchange flow, every agent delegation chain makes an authorization decision — and each one currently defines it as "local policy: out of scope." AuthZEN is the interoperable standard for that decision. Swap OPA for OpenFGA, swap WIMSE's InMemoryPDP for a cloud-hosted policy engine — the interface stays identical.
Where You've Seen This Pattern Before

AuthZEN formalises a pattern that Envoy and Kubernetes already use — AuthZEN adds a standard API so any PDP can be plugged in.

Envoy ext_authz filter
Envoy intercepts every HTTP request and calls an external authorization service via gRPC or HTTP before forwarding it. The authorization service is a PDP — it receives the request context (headers, path, method) and returns allow/deny. AuthZEN standardises exactly that PDP API — any Envoy ext_authz backend could expose /access/v1/evaluation instead of a custom proto.
Kubernetes Admission Controllers
Kubernetes webhook admission controllers intercept API requests to kube-apiserver before they're persisted. The webhook is a PDP — it receives the AdmissionRequest (subject, resource, operation) and returns allow/deny in an AdmissionResponse. AuthZEN is the same pattern at the OAuth layer — the token issuance request is the admission request, the AuthZEN PDP is the webhook.
OAuth AS — the new enforcement point
RFC 8693 Token Exchange and Txn-Token issuance have always had a PDP decision at their core — they just called it "local policy." draft-gazitt-oauth-authzen-token-exchange maps that decision to AuthZEN: subject=workload SPIFFE ID, resource=token audience, action=exchange. This PoC implements that mapping live in WASM.
What This PoC Demonstrates
Live in your browser (WASM)
All evaluation runs in Go compiled to WebAssembly — no server, no network call. The InMemoryPDP evaluates real AuthZEN five-tuple requests using glob-matched policy rules with priority-based conflict resolution.
OpenFGA export → play.fga.dev
Any set of policy rules can be exported as an OpenFGA DSL model + YAML tuples. Copy and paste into play.fga.dev to run the same evaluation in OpenFGA's relationship-graph engine.
OPA/Rego export → play.openpolicyagent.org
Rules are exported as Rego v1 policy. The OPA tab generates the policy + a matching input document and attempts to open the OPA playground directly with the policy pre-loaded.
AS Bridge simulation
The AS Bridge tab simulates an OAuth Authorization Server calling AuthZEN during an RFC 8693 Token Exchange — showing the exact five-tuple mapping from draft-gazitt-oauth-authzen-token-exchange.
Live AuthZEN Evaluation

Construct a POST /access/v1/evaluation request and evaluate it against the in-memory PDP. All cryptographic operations run in Go/WASM — no server involved.

Quick scenarios
alice reads document
alice edits budget
bob reads private doc (deny)
orchestrator exchanges token
sub-agent exchanges for cloud-b
sub-agent exchanges for cloud-c (deny)
admin workload full access
Subject
type
id
Resource
type
id
Action
name
Strong Auth: Passkey Subject (WebAuthn Level 4)

The same authorization policy yields different decisions depending on the strength of the subject's authentication. A passkey (WebAuthn L4) unlocks actions that an anonymous caller or API key cannot perform — without changing the policy, just the subject type.

Tier 0
anonymous
no credential
Tier 2
api_key
shared secret
Tier 3
oauth_token
OIDC / SSO
Tier 4 ★
webauthn
passkey (L4)
Tier 5
spiffe
workload mTLS
○
Register passkey credential (WebAuthn L4, device-bound, ES256 / P-256)
○
Evaluate: webauthn subject → classified-q3-report can_read → PERMIT ✓
○
Compare all tiers: anonymous / api_key / oauth_token → DENY  |  webauthn → PERMIT
The policy rule hasn't changed — only the authentication strength of the subject. This is the AuthZEN pattern: authentication and authorization are separate concerns, and stronger auth unlocks stronger access.
WebAuthn Level 4 today: ES256 / RS256 / EdDSA only — no PQC COSE algorithm types yet. A future WebAuthn L5 or COSE extension is required for ML-DSA-65 passkeys. See W3C WebAuthn L4 · NIST FIPS 204 (ML-DSA) pending COSE registration.
Under the hood — how the stronger subject is created
Passkey Registration Ceremony (WebAuthn L4)

The webauthn subject type that unlocked the PERMIT decision above comes from a specific registration ceremony. AuthZEN does not define how that credential is created — it only consumes the subject type. Here is what happens on the authentication side to produce a credential the PDP can trust at tier 4.

① RP Server
authzen-poc.example
Generates random challenge (32 bytes)
Sends publicKey options
Stores: credentialId + publicKey
challenge →
← credential
② Browser JS
navigator.credentials
Calls credentials.create()
Forwards options to OS
Returns PublicKeyCredential
→ create()
← attObj
③ Authenticator
phone / hardware key
User verifies (biometric/PIN)
Generates EC P-256 key pair
Private key never leaves device
THE BROWSER CALL — navigator.credentials.create()
const credential = await navigator.credentials.create({
  publicKey: {
    rp:      { name: "authzen-poc.example", id: "authzen-poc.example" },
    user:    { id: new Uint8Array(16),         // opaque user handle
               name: "alice@example.com", displayName: "Alice" },
    challenge:        crypto.getRandomValues(new Uint8Array(32)),  // from RP — binds ceremony
    pubKeyCredParams: [{ type: "public-key", alg: -7 }],  // -7 = ES256 (COSE)
    authenticatorSelection: {
      residentKey:      "required",    // device-bound credential → tier 4
      userVerification: "required"     // biometric or PIN mandatory
    },
    attestation: "none"                     // no device attestation needed here
  }
});

// What you get back:
credential.id                           // base64url credential ID — store this
credential.response.attestationObject   // CBOR: authData + credentialPublicKey
credential.response.clientDataJSON      // challenge echo + origin (JSON)
attestationObject (CBOR)
rpIdHash — SHA-256 of the origin domain
flags — UP (user present) + UV (verified)
credentialId — random opaque identifier
credentialPublicKey — CBOR-COSE EC P-256
sig — authenticator self-attestation
RP stores two things only
credentialId — links future assertions to this key
publicKey — EC P-256 — verifies assertion signatures

Never stored: private key (hardware-bound),
password, PIN, biometric template
Simulation note. This demo runs via WebAssembly — there is no real authenticator. Go's crypto/ecdsa generates the P-256 key pair and crypto/rand produces the credential ID, giving cryptographically valid output. The private key lives in WASM memory rather than a hardware secure enclave — the ceremony structure and the math are identical.
Can someone tamper the subject and claim type: "webauthn" without doing the ceremony?
In this demo: the PDP trusts the subject as-is. Nothing stops a caller from POSTing {"subject":{"type":"webauthn","id":"fake"}} directly — the authorization layer does not verify that the ceremony was actually completed.

In production, the ceremony closes this gap cryptographically:
  • The authenticator signs a server-issued challenge with the hardware-bound private key. You cannot forge this — the key never leaves the device.
  • The authentication server verifies that signature, then issues a signed JWT asserting the subject with amr: ["hwk"] (hardware key) and acr: "L4".
  • The AuthZEN PDP only trusts subject types that arrive inside a token it can verify the signature on — not raw caller-supplied JSON. A forged type: "webauthn" claim without a valid token signature is rejected.
Browser → WebAuthn assertion → AuthN server verifies sig → issues JWT (amr:["hwk"]) → AuthZEN PDP checks JWT sig → trusts tier 4
The ceremony is the proof. The amr claim is the attestation. The JWT signature is the tamper-evident seal.
Policy Rules Editor

Edit the in-memory PDP rules. Changes immediately affect evaluations in all tabs. Rules are evaluated by priority (higher = first); ties broken by position (later wins). No match → default deny.

#LabelSubject typeSubject ID (glob) Resource typeResource ID (glob)Action (glob) DecisionPriority
Export Policy

Export the current rules to OpenFGA DSL or OPA Rego. Open the respective playground tab to run them interactively.

Bulk Evaluations — POST /access/v1/evaluations

AuthZEN §5.2: a single request carries a shared Subject plus an array of (Resource, Action) pairs. The PDP evaluates each independently and returns an array of decisions. Useful for permission checks across multiple resources in one round-trip.

Shared Subject
type
id
Evaluations (Resource type · Resource ID · Action)
AS Bridge — OAuth Token Exchange → AuthZEN

Implements the mapping defined in draft-gazitt-oauth-authzen-token-exchange-00

When an Authorization Server receives an RFC 8693 Token Exchange request, it must decide whether to issue the requested token. This simulation shows how the AS constructs an AuthZEN evaluation request from the exchange parameters and calls the PDP to get its decision — replacing the "local policy" black box with a standard API call.

Five-tuple mapping (per draft §3): subject_token.sub → subject.id  ·  audience → resource.id  ·  "exchange" → action.name  ·  subject type = "workload"  ·  resource type = "token-audience"
orchestrator → cloud-b (allow)
sub-agent → cloud-b (allow)
sub-agent → cloud-c (deny)
unknown workload (deny)
Subject token
Target audience
SPIFFE ID (override)
OpenFGA — Relationship-Graph PDP Backend

openfga.dev/docs/interacting/authzen  ·  play.fga.dev  ·  OpenFGA DSL schema 1.1

OpenFGA exposes a built-in AuthZEN-compatible endpoint at POST /stores/{id}/access/v1/evaluation. The same five-tuple request you send to this PoC's in-memory PDP can be sent directly to OpenFGA — the difference is the backend evaluates using a relationship graph (Google Zanzibar model) rather than glob rules.

AuthZEN five-tuple → OpenFGA triple: subject.type:subject.id → user  ·  action.name → relation  ·  resource.type:resource.id → object
Generate OpenFGA Model from Current Rules

The current InMemoryPDP rules are exported as an OpenFGA DSL model and YAML tuple set. Copy both into play.fga.dev to run the same evaluations in OpenFGA.

OPA — Open Policy Agent Rego Backend

play.openpolicyagent.org  ·  Rego v1 language reference

OPA evaluates AuthZEN five-tuple requests as Rego policy input. The generated policy accepts the same { subject, resource, action } structure and returns decision := true/false. Use the OPA playground to test the generated policy interactively.

Generate Rego Policy from Current Rules
AWS Lambda Authorizer + AuthZEN

Lambda Authorizers are a natural PEP — AuthZEN standardises exactly what happens inside that function.

Traditional (hardcoded logic)
With AuthZEN PDP
Client API Request GET /orders API Gateway invokes authorizer TTL cache: 300s token + ctx Lambda Authorizer if token.sub == "alice" return Allow Hardcoded policy logic IAM policy doc Allow {Effect: "Allow"} methodArn: "*" 200 OK / 403 Built-in TTL cache (up to 3600s) AuthZEN PDP only called on cache miss — decouples auth load from request rate
What Lambda provides
Built-in TTL decision cache (default 300s, max 3600s). AuthZEN PDP only called on cache miss — natural load decoupling at scale.
What AuthZEN adds
Lambda becomes a thin PEP translation layer: extract five-tuple from request context, call AuthZEN, translate {decision:bool} to IAM policy doc.
The mismatch to handle
AuthZEN returns a single boolean. Lambda Authorizer returns an IAM policy document covering one or many ARNs. Use bulk evaluations to pre-check multiple resources in one PDP call.
Centralized vs Decentralized — Resilience and HA

AuthZEN defines the API contract, not the deployment topology. Choose your resilience pattern.

Centralized (SPOF risk)
Active-Active HA
Sidecar / per-pod
All services call one AuthZEN PDP endpoint — simple to manage, single point of failure Service A PEP Service B PEP Service C PEP AuthZEN PDP Single instance SPOF risk
Centralized PDP risk: A single AuthZEN endpoint in the request critical path is a SPOF. If the PDP is unreachable: fail-closed = all requests return 403; fail-open = all requests pass (with audit log). Mitigate with circuit breaker + decision cache before calling PDP.
Performance Considerations

Adding a synchronous AuthZEN call to the critical path has cost. Here are the mitigation patterns.

Decision Cache
Sequential vs Bulk
Fail-open / Fail-closed
Token Exchange Critical Path
Network round-trip
PDP evaluation
Cache lookup
Cold (cache miss)
~20ms
Warm (cache hit)
<0.5ms
After TTL expiry
~20ms
Cache key: hash(subject_id + resource_type + resource_id + action_name)
TTL tradeoff: short (5s) → frequent PDP calls; long (60s) → stale decisions after policy change.
Gap in current spec: AuthZEN 2.0-ID2 has no cache-invalidation mechanism — the PDP cannot push "revoke this decision." This is an open WG discussion.
Standards Tracker

Last checked: 2026-08-07  ·  standards-baseline.json  ·  standards-tracker.yml

StandardWG / BodyStatusPoC impl. RevFilesImpact
AuthZEN Authorization API OpenID Foundation ● WG Draft ✓ Implemented 2.0-ID2 pkg/authzen/types.go, handler.go, pdp.go Core spec — five-tuple types, /access/v1/evaluation, /access/v1/evaluations endpoints, decision response shape
AuthZEN Profile for OAuth 2.0 Token Issuance IETF OAuth WG ● Individual draft ◌ Monitoring -00 pkg/authzen/pdp.go Defines the canonical AuthZEN five-tuple for OAuth token issuance decisions. Our InMemoryPDP implements the decision evaluation model this draft formalises.
AuthZEN Binding for OAuth 2.0 Token Exchange IETF OAuth WG ● Individual draft ✓ Implemented (AS Bridge) -00 cmd/demo-wasm/main.go The AS Bridge tab implements the exact five-tuple mapping: sub→subject.id, audience→resource.id, "exchange"→action.name
RFC 8693 — OAuth 2.0 Token Exchange IETF OAuth WG ● Published RFC ✓ Implemented (AS Bridge) RFC cmd/demo-wasm/main.go Foundational. AS Bridge simulates the exact RFC 8693 request format (grant_type, subject_token, audience). AuthZEN fills the "governed by local policy" gap.
OpenFGA DSL — Authorization Model OpenFGA / CNCF ● Stable ✓ Export implemented 1.1 pkg/authzen/export.go ToOpenFGA() generates schema 1.1 DSL model + YAML tuples from InMemoryPDP rules. OpenFGA tab links to play.fga.dev.
OPA Rego — Policy Language CNCF OPA ● Stable (v1) ✓ Export implemented v1 pkg/authzen/export.go ToRego() generates Rego v1 policy from InMemoryPDP rules. OPA tab generates policy + tries to open play.openpolicyagent.org with it pre-loaded.
Automated Standards Tracking
Daily cron
GitHub Actions checks IETF Datatracker daily for authzen-issuance and authzen-token-exchange revisions. Opens a labelled issue when any revision changes.
OpenID AuthZEN spec
Checks the openid/authzen GitHub repo for new releases/tags. The AuthZEN spec is maintained on GitHub, not IETF Datatracker.
Same pattern as wimse projects
Same standards-baseline.json + check_standards.py pattern as wimse-agent-fabric and wimse-identity-fabric. Consistent issue format across all three repos.