Authorization
This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at README.md, and llms.txt lists every page.This is the single-silo introduction to the Orleans.Lattice authorization layer, driven by a self-contained demo token. To authenticate a real Microsoft Entra ID user on a single silo, see the Entra Authorization sample. For a multi-cluster deployment that also converges policy and membership across sites, see the Cross-Cluster Authorization sample, which builds on the same layer.
What it shows
The Orleans.Lattice authorization layer on one in-process Orleans silo, with
a focus on group and nested-group membership: identity (users, groups, and a
group nested inside another group), a default-deny policy, and per-tree /
per-key / per-prefix rules enforced on every operation. There is no replication,
no gRPC, and no web host - just the authorization you get from
AddLatticeMembership + AddLatticeAuth.
The membership graph is deliberately nested:
staff (top-level group)
|
+-- engineering (a group that is a member of staff)
|
+-- alice (a user)
Because group membership is transitive, alice belongs to both engineering
and staff even though she was only ever added to engineering. A rule that
grants staff read therefore reaches alice through the nesting.
The demo runs in four acts:
- Nested membership. Create groups (
staff,engineering,oncall), makeengineeringa member ofstaff, addalicetoengineeringandbobtooncall(users are plain member ids - the directory keeps no user records - andcarolstarts in no group), and print each subject's transitive groups -aliceshows up instaffeven though she was only added toengineering. - Rules and enforcement. Author three rules under a default-deny policy - a
tree grant on the top-level group (
staffreads the whole tree), a prefix grant on the nested group (engineeringwrites/deletes thesvc/subtree), and a key grant on a flat group (oncallreads/writes one incident key). Then exercise reads, writes, and deletes as each subject and watch the gate allow or deny each one.carol, who is in no group, is denied everything. - Read visibility. A range read returns only the keys the caller is
authorized to read -
alice(staff) sees every key,bob(oncall) sees only the one incident key it may read,carolsees nothing. A point read of an unauthorized key is soft-denied (returns absent, not an error). - Runtime grant via nesting. Add
caroltoengineeringat runtime. She immediately inheritsstaffthrough the nested edge and gains both the staff tree-read and the engineering prefix-write - with no per-user rule authored for her.
Every subject's operations run under an ambient credential
(LatticeCredentialContext.Use) that flows to the grains on the Orleans request
context; a small custom ILatticeCredentialAuthenticator maps a demo token to a
subject id, and the membership directory expands the subject's group memberships
transitively (walking nested groups). The groups, memberships, and rules are
written through the silo-side membership directory and policy store, which run
under system origin and never consult the gate, so they need no prior grant;
the tree's data keys are seeded as the configured bootstrap administrator,
which the gate allows before any rule exists.
Run it
dotnet run --project samples/Authorization
Expected output
Silo starting... ready.
== Act 1: build a nested membership graph ==
alice's transitive groups: {engineering, staff} (staff inherited via engineering)
bob's transitive groups: {oncall}
carol's transitive groups: {}
== Act 2: author default-deny rules and enforce them ==
As alice (engineering -> staff):
read audit/log -> 'seeded' (staff tree-read)
write svc/api/status -> allowed (engineering prefix-write)
write incident/current -> DENIED (not oncall -> deny)
As bob (oncall):
write incident/current -> allowed (oncall key grant)
write svc/api/status -> DENIED (not engineering -> deny)
delete svc/api/status -> DENIED (not engineering -> deny)
As carol (no groups):
read audit/log -> (hidden) (soft-denied)
write svc/api/status -> DENIED (deny)
== Act 3: read visibility (point + range) ==
Range read of the whole tree returns only authorized keys:
alice sees 4 keys (staff: all)
bob sees 1 keys (only the incident key it may read)
carol sees 0 keys (nothing)
== Act 4: grant access by joining a nested group at runtime ==
Before: carol range read sees 0 keys; write svc/api/status -> DENIED
Added carol to engineering. Her transitive groups: {engineering, staff}
After: carol range read sees 4 keys; write svc/api/status -> allowed
[OK] nested-group membership granted carol read + write with no per-user rule.
When to use
- Single-cluster deployments that need per-tree, per-key, or per-prefix authorization with a default-deny posture and group-based subjects.
- Any deployment that wants to grant access by group, including nested groups (roles composed of roles), rather than authoring a rule per user.
When not to use
- Multi-cluster deployments that must apply a single authorization policy uniformly across regions - see the Cross-Cluster Authorization sample, which enrols the membership and policy system trees into cross-cluster replication so a grant or revoke authored in one region becomes effective in the others.
- Deployments that do not need authorization at all. The layer is opt-in; a host
that never calls
AddLatticeAuthpays nothing on the data path.
Notes on this sample
- The bootstrap administrator (
root-admin) seeds the demo data before any rule grants access; the reserved membership and policy trees are written by the silo-side directory and policy store under system origin, not through it. Production should keep the bootstrap set as small as possible and grant everything else through rules. - Enforcement reads a compiled policy snapshot that rebuilds off the policy-tree change feed, and group membership is resolved from the directory, so the sample polls briefly after authoring rules and after the runtime membership change before asserting the new outcome.