FerrisKeyDocs

What is AuthZEN?

OAuth2 and OIDC handle authentication and coarse-grained delegation. They don’t really handle authorization, in the sense of “can this specific user do this specific thing to this specific resource right now?”

That’s the question AuthZEN tries to answer in a standard way.

The fine-grained authorization problem

Coarse authorization is easy:

user.role == "admin"  →  allow

But real systems quickly outgrow it. Real questions look like:

  • “Can Alice edit the invoice with ID 4271 in the finance workspace?”
  • “Can this support agent view customer records for EU customers only?”
  • “Can this shared link holder download for the next 24 hours?”

These decisions depend on the user, the action, the resource, the resource attributes, the context (time, location, MFA level), and business rules that often live outside the IAM system.

This is called fine-grained authorization (FGA), or sometimes attribute-based access control (ABAC). For most of the last decade, it has been every team’s homegrown mess.

The interop problem

Every fine-grained authz tool speaks a different dialect:

  • One wants YAML files of allow/deny rules.
  • Another wants a relationship graph.
  • A third wants a policy language with its own DSL.

Change tools, rewrite everything. Run multiple apps, and they often pick different tools and end up unable to talk to each other.

AuthZEN is a specification from the OpenID Foundation that tries to fix this. It defines a wire-level API for asking and answering authorization questions. Different policy engines can implement the same API, and applications can switch between them, or talk to several at once, without changing a line of code.

What AuthZEN looks like

The core idea is a request/response shape:

POST /access/v1/evaluation
{
  "subject": { "type": "user", "id": "alice@example.com" },
  "action":  { "name": "edit" },
  "resource": {
    "type": "invoice",
    "id": "4271",
    "properties": { "workspace": "finance" }
  },
  "context": { "mfa": true }
}
{ "decision": true }

Read it as: “may subject S perform action A on resource R in context C? Yes or no.”

AuthZEN does not say how the engine reaches that decision. That’s up to the policy engine, whether OPA, Cerbos, Cedar, OpenFGA, or something else. AuthZEN only standardizes the conversation.

Why this matters

If AuthZEN takes hold:

  • Vendor swap. Try Cedar, decide you want Cerbos, swap the URL. Policies still need migrating, but the apps don’t.
  • Multi-engine deploys. Use one engine for relationship-based queries and another for attribute-based rules. Apps speak one wire protocol either way.
  • Tooling and gateways. API gateways, service meshes, and IAM platforms can speak AuthZEN to any policy engine.

AuthZEN vs OAuth2 scopes

A scope is a coarse capability label: “this token can read orders.” Useful as a first filter.

A fine-grained authorization decision asks: “can this user read this order right now?” Scopes can’t express that without exploding into hundreds of named scopes.

The healthy pattern: scopes gate the API at the door (is this token even allowed here?); AuthZEN-style decisions happen inside the API (and is this specific action allowed?).

A mental model

OAuth2 scopes are the bouncer at the front door: token has the right sticker, you can come in. AuthZEN is the manager inside: “you’re allowed in the building, but this particular room? Let me check the schedule, the booking, and whether you have your badge upgraded today.”

Coarse and fine, working together.

Where to go next

In FerrisKey

FerrisKey is an AuthZEN 1.0 policy decision point, one per realm, with Cedar as the engine behind it. You upload a Cedar schema and write policies. An application authenticated as a service account then sends a request shaped like the one above to POST /realms/{realm_name}/authzen/access/v1/evaluation, naming resource types with the schema’s namespace (App::Invoice rather than invoice), and FerrisKey answers { "decision": true } or { "decision": false }. A second endpoint takes up to 100 questions in one call, and a metadata document at /.well-known/authzen-configuration/realms/{realm_name}/authzen tells a client where both live.

The identity half stays with FerrisKey. When the subject is a FerrisKey::User, its roles, groups, organizations and attributes are read from the realm, so policies can use what you already manage. If you run another engine, nothing changes for it: the scopes, roles and claims in FerrisKey’s tokens are still the input.

The module is off until the operator sets AUTHZ_ENABLED, and each realm switches it on for itself.