Security: identity, authorization, and enforcement
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 security.md, and llms.txt lists every page.Orleans.Lattice ships an opt-in security layer that turns an anonymous, allow-all key-value store into an authenticated, authorized one. It is composed of several small add-on packages that layer cleanly on top of the core library. This page is the map: it explains what each capability does and links to the package documentation that covers it in depth. Nothing here is enabled unless the host registers it, and a cluster that registers none of it keeps the core read/write path byte-for-byte unchanged (see Zero cost when absent).
The pipeline at a glance
A gated operation flows through four stages. Each stage is a separate package so a deployment adopts only what it needs:
- Identity turns the credential a caller presents into a stable subject (a subject id plus the transitive closure of the groups it belongs to).
- Authorization compiles durable rules into a decision: given a subject, an operation, and a tree key or range, allow or deny.
- Enforcement consults that decision on the core data path for every user-originated operation, fail-closed: a denied write throws and a denied point read reports absent.
- External surfaces project the same gated data and control planes to non-.NET callers, operators, and the Explorer - all authorizing through the very same gate, never a bespoke bypass.
Identity and membership
Orleans.Lattice.Membership owns a durable,
introspectable directory of users and groups (with nested, group-in-group
membership) and a credential-to-subject resolution pipeline. Authentication is
pluggable: a credential is mapped to a principal by one or more scheme-selected
ILatticeCredentialAuthenticators. The package ships an anonymous authenticator
and a built-in JWT authenticator registered per trusted issuer; a host can
register its own. Resolution is cached with a configurable TTL.
Three optional companions integrate a corporate identity provider:
Orleans.Lattice.Membership.Entra- a Microsoft Entra ID (Azure AD) credential authenticator. Its Azure CLI setup guide provisions an app registration and shows the host wiring end to end.Orleans.Lattice.Membership.Entra.Graph- a Microsoft Graph-backed resolver for subjects whose group claims overflow the token.Orleans.Lattice.Membership.Oidc- a generic OpenID Connect credential authenticator for any conformant provider (Okta, Auth0, Keycloak, Ping, Google), configured from the provider's discovery document. It is an additive sibling to the Entra authenticator: neither package depends on the other, and a silo can register both.
Membership on its own adds identity resolution and the directory; it enforces nothing until the authorization package is also registered.
Authorization: policy and decisions
Orleans.Lattice.Auth adds the durable policy
store, the decision engine, and the enforcing access gate. Rules grant or deny a
set of operations to a subject selector (a user or a group) at a scope (a whole
tree, a key prefix, or a single key). When several rules match, the engine
resolves them deterministically: the most-specific scope wins; within a scope
tier a user rule outranks a group rule (on by default, configurable), and
otherwise deny overrides allow; with no matching rule the configured default
effect applies. Cluster-wide capabilities that do not attach to a tree - such as
telemetry and app installation - are granted by a whole-tree rule on the
cluster-wide * scope. An opt-in all-trees tier
(LatticeAuthOptions.AllTreesGrantsEnabled, off by default) also lets such a
cluster-wide rule govern ordinary trees: an all-trees deny then wins outright, and
an all-trees allow applies only where the tree's own rules decide nothing. The
recommended and default
posture is default-deny. A small set of bootstrap administrators forms
the root-of-trust that seeds the first rules and performs break-glass operations.
Consistency: eventual by default, strict on request
Policy propagation is eventually consistent by default: a rule change takes effect once the destination's compiled snapshot rebuilds off the updated policy tree, which happens continuously in the background. A tree whose writes must not run ahead of a policy change can opt into a strict epoch fence by naming the tree in the strict-consistency set: a user write to it is then rejected while this cluster's compiled policy is older than a floor the caller stamped, and reads are never fenced. Strict behaviour is opt-in and off by default. The trade-offs are covered in the authorization README.
Enforcement on the data path
The core library exposes an access-gate seam that defaults to an allow-all null
gate. Registering the authorization package replaces it with the enforcing gate,
which every user-originated core read and write consults; the routing lookup
GetRoutingAsync, which resolves a tree's physical id and shard map for the
library's own coordinators, is deliberately ungated. Enforcement is fail-closed
throughout: writes and deletes throw on denial, point reads of a denied key
report absent, and range scans prune to the authorized subset server-side, while
a read that cannot be narrowed per key - CountPerShardAsync, a projection
digest, or a whole-tree report such as DiagnoseAsync - throws
LatticeAuthorizationDeniedException under a partial grant as well as a deny. A
range delete under partial authorization is hard-denied rather than silently
narrowed, so a caller never deletes a subset while believing it deleted a range.
The State API honours the same read-access visibility when membership and
authorization are registered, so a browsing operator only sees the keys the
subject may read.
Alias changes carry one further, ownership-based check that caller rights cannot
override: every alias assignment - resize, restore and remediation included - is
put to the core ITreeOwnershipGuard seam, which allows every alias unless the
host registers an ownership provider (the apps package
does), and a refusal throws LatticeTreeOwnershipDeniedException. See
Tree Registry.
External surfaces
Transport-agnostic facades, each with a code-first gRPC binding, extend the cluster to callers that do not embed the Orleans client, and every one of them authorizes through the core gate rather than re-implementing authorization. Three of them are this layer's own surfaces:
Orleans.Lattice.Api.Authand its gRPC binding - the control plane: administer membership and policy, and explain decisions. Every operation is administrator-gated through the same enforcement primitive the data path uses, andExplainAsyncproduces its verdict from the same gate. The subject it evaluates is rebuilt from the membership directory (the named id and its transitive directory groups), so token-asserted or claim-projected groups are not part of it, and under theTokenOnlygroup-merge mode the explained subject can differ from a live caller's.Orleans.Lattice.Api.Data- the write-capable external data plane for non-.NET clients. It routes every call through the gatedILatticesurface, so per-tree and per-key rights are enforced automatically; a coarse transport-level authorizer that denies by default sits in front as an endpoint on/off switch.Orleans.Lattice.Api.State- the read-only state-query surface, which honours the same read visibility.
The other facades - tree administration, backup, replication, schema, tenant administration, app installation, and telemetry - authorize their operations through the same gate, and the Model Context Protocol endpoint projects those facades as agent tools. The full set is listed in PACKAGES.md.
The Explorer
runs an extensible login challenge against an auth-enabled State API endpoint,
and its sign-in mechanisms are a
provider model a host can
extend. Microsoft Entra ID sign-in ships as two such providers:
Orleans.Lattice.Explorer.Entra, an
interactive MSAL sign-in for hosts that do not use the hosted-web OpenID Connect
cookie flow, and
Orleans.Lattice.Explorer.Entra.Web
for the hosted web console.
Cross-cluster convergence
The membership directory and the policy store are ordinary ILattice trees, so
they replicate through the replication package's
system-tree enrolment. This
is a separate, explicit opt-in from data replication: a rule authored or
revoked in one cluster converges to the others, in both state and enforcement.
Some deployments deliberately keep policy local (per-region security domains,
data-residency boundaries, or blast-radius containment); the trade-offs are
discussed in that document.
Observability and audit
Every authorization decision, its latency, and the compiled-snapshot epoch and age are published on a single meter, and an optional durable audit sink records a decision trail. The full instrument catalogue and the audit-sink seam are documented in Authorization observability. The subject-resolution cache belongs to the membership package, so its hit and miss counters live on the membership meter and are documented in Membership observability.
Security posture and cost
The security posture page is the authoritative reference for the threat model, the attack surface, the fail-closed guarantees, the internal-grain trust boundary, TLS expectations, the security-review findings with their resolutions, and the measured per-operation cost of enforcement.
Zero cost when absent
Every layer above is opt-in. The core registration installs only the allow-all null gate, whose decision is a synchronously-completed, allocation-free allow that never resolves a subject. A cluster that never registers authorization keeps that null gate, so the data path is byte-for-byte what it was before the security layer existed.
Registration order
The layers must be registered in dependency order on the silo: the core lattice first, then membership, then authorization, then any external facade. Each add-on validates its prerequisites and fails fast at registration with an actionable message rather than failing obscurely at silo start. The exact calls are shown in each package README linked above.