# How authorization works Everything that decides who may do what in your product, on one page: the organization tree, memberships, profiles, grants, groups, denies, field policy, the node each request acts at, and elevation. Each section links to the guide that puts it to work. The short version: **a request acts at one node of your product's organization tree, with the authority its membership holds there, and the platform reads that authority on every request.** Your end user's token says only who they are; what they may do comes from the tree, never from the token and never from your code. A change to a member's authority takes effect within seconds, on the requests that follow, not at the next sign-in. ## The picture ```text / the root: your product's base profiles live here (config/profiles.toml) └── /org Ada's membership is homed here: roles admin, staff, customer ├── /org/customers Bea signed up here as a customer, and owns her own orders └── /org/acme a client node; deny: entity.delete on orders, binding Acme and below ├── /org/acme/sales Carl: membership at /org/acme, grant "editor" here until 31 Jan └── /org/acme/ops a group "night-shift" holds a grant of "viewer" here a request ─► acts at ONE node ─► denies there and above ─► roles there ─► owner / shared ─► field policy (its membership's (refuse first, (home roles (which records) (which fields) home, or the entry's whatever grants + grants at declared org_path) confer) or above it) ``` ## The pieces - **Nodes.** Your product's end users and their records live in a tree of nodes such as `/org`, `/org/customers` and `/org/acme`. A record is stored at the node its writer acts at. A session reaches the records at its node and below; an entity can narrow that to each user's own records or open an ancestor's records for reading ([Owner and shared entities](entities.md)). You create client nodes with `aircloud org nodes create` ([Members, roles and registration](members.md#nodes)). - **Memberships.** A person holds one or more memberships in your product. Each membership is homed at one node and holds roles there. One person's memberships never sit one inside another. Every request acts through one membership: a session switches it at `/_air/memberships`; the CLI takes `--membership` when you hold several ([Members, roles and registration](members.md#memberships-and-their-ids)). - **Roles** are the keys of your `[authenticate] roles` vocabulary. A route admits a session holding any of its roles ([Routes](routes.md)); an entity's `write_roles` decide who may write it ([Owner and shared entities](entities.md#roles-each-write-requires)). - **Profiles** are named sets of roles. The base profiles are declared in `config/profiles.toml`; runtime profiles are created at a node below the root ([Profiles and grants](profiles-and-grants.md)). - **Grants** confer a profile, by reference, at a node at or below a membership's home, optionally until a time. Changing a profile changes every grant that references it. - **Groups** are memberships that share grants: a group's grant reaches each member at or below both the grant's node and the member's own home. - **Denies** remove roles or refuse operations at and below a node, whatever any grant confers, and they bind every actor there: members, functions acting there, and you. - **Field policy** decides, per record, which fields are visible, writable and required ([Field policy](field-policy.md)). - **The acting node.** A function with no end user acts at the `org_path` its route or trigger declares, and may narrow to a node below it, never above ([The acting node and elevation](acting-node.md)). - **Elevation.** A declared aggregate may count or sum an owned entity across its owners without ever naming a record or an owner ([The acting node and elevation](acting-node.md#elevated-aggregates)). - **Workspaces.** A product may let any signed-in end user create a company of their own: a node and a new membership homed there ([Workspaces](workspaces.md)). ## Who decides, in what order The platform evaluates a request in this order. The first step that refuses ends it; `aircloud authz explain` prints which step decided and the `rule` it names. | step | what it reads | refuses with | the `rule` explain names | |---|---|---|---| | the membership | the selected membership, against the projection | `session_membership_required` (a session), `membership_required` (a direct caller) | — | | the node is active | the acting node and every ancestor | `org_node_archived` | — | | denies | every deny at the acting node and above | an op-deny refuses the operation; a role-deny removes the role before the next steps | `deny.op`, `deny.role` | | roles | the membership's home roles, its grants and its groups' grants at or above the record's node | `session_role_required` (a route's roles), `flex.role_required` (`write_roles`) | `home`, `grant.membership`, `grant.group`, `write_roles` | | records | `owner`, `shared` and the tree | a record outside the scope is not found | `owner` | | fields | the `[[policy]]` rules at the record's node | a hidden field is omitted; a non-writable change is `flex.forbidden` | `field_policy` | When the authority cannot be read in time, the request is refused (`session_authz_unavailable` on a session), never treated as "no restriction". ## Asking why ```sh aircloud authz explain --sub 2891… --plane end_user --node /org/acme --action entity.delete --entity orders aircloud authz who --node /org/acme aircloud members review --window 720h ``` `authz check` answers the decision and exits 1 unless it is an allow; `authz explain` prints the whole answer: the membership, its roles and scopes at the node, the step that decided and the record layer's detail. `authz who` lists every membership whose authority reaches a node, with its roles there. Each is answered by the same evaluator enforcement uses, over the nodes at or below yours. Your coding agent asks the same question through the dev MCP server's `authz_check` ([The dev MCP server](mcp.md)). --- Rendered from aircloud 0.1.12 at commit d84a0ce6dae53629277f656d96d81549df36807e.