Updated 2026-10-18 for Loams 0.1.0
Changed: authentication now ships in 0.1.0 (Tailscale identity, OIDC, API keys), and Live and Graph check per-app, per-table and per-graph scopes; roles, per-namespace authorization and OpenFGA are still planned; linked the security model page.
Planned When this was written Loams had no authentication or authorization on any listener; one plan was to add both. Update for 0.1.0: engine auth is available (Tailscale identity by default, OIDC with JWTs checked against the issuer's JWKS, hashed API keys, or loopback-only), Loams Live authorizes every call per app and per table, and Loams Graph checks reader, writer and admin per graph. Roles, per-namespace authorization and tenant quotas, and the OpenFGA authorizer below, are still Planned. The security model page describes what 0.1.0 enforces. This post covers the authorization design and the project it builds on.
OpenFGA is an authorization service inspired by Google's Zanzibar paper. It answers one question very fast and at scale: does this user have this relation to this object? Loams plans to use it as its fine-grained authorizer, shared with Lakekeeper, and as the authority behind every token in the function runtime.
| Repository | openfga/openfga |
| Docs | openfga.dev |
| License | Apache-2.0 |
| Version checked | v1.21.0 |
| Foundation | CNCF, incubating since October 2025 |
| Rust client | openfga-client 0.6, Apache-2.0, maintained by Vakamo (Lakekeeper's vendor) |
| In Loams | Planned |
How OpenFGA works
Authorization data is a set of relationship tuples: (user, relation, object), such as (user:ana, member, org:acme) or (org:acme#member, reader, namespace:acme/docs). An authorization model says what the relations mean, in a small DSL:
model
schema 1.1
type user
type org
relations
define member: [user]
define admin: [user]
type namespace
relations
define org: [org]
define reader: [user, org#member] or writer
define writer: [user] or admin from orgRelations compose. reader above is anyone granted directly, any member of an org granted as a group, or anyone who is a writer. admin from org follows the namespace's org relation and takes that org's admins (a tuple-to-userset rewrite, in Zanzibar's terms). A Check walks those rules over the stored tuples to answer yes or no. ListObjects answers "which namespaces can Ana read?", ListUsers answers "who can read this namespace?", and BatchCheck answers many checks in one call. Tuples live in Postgres, MySQL or SQLite. Reads can ask for lower latency or for higher consistency right after a write.
The appeal is that authorization becomes data plus a model instead of code scattered across services: sharing, groups, inheritance and delegation are tuples, and one query answers them the same way everywhere.
Why Loams plans to use it
Loams’ tenancy has organisations, members, teams, projects, environments (namespaces), agents and service accounts, and it has to support delegated grants in the managed cloud and in customers' own clusters. Role-based access control covers simple installs, and Loams will ship built-in roles. Multi-org tenancy, "share this collection with that team", and agents that may touch only what their owner allows want relationships.
OpenFGA is Apache-2.0, CNCF-hosted, runs as a separate service, and has a proven model to start from: Lakekeeper, the Iceberg catalog Loams will use for tables, already authorizes with OpenFGA. Sharing one OpenFGA store with Lakekeeper means one grant covers a namespace's collections and its Iceberg tables.
How Loams would use it
An Authorizer trait in the engine, in the style of Lakekeeper's, that every surface calls: check(principal, action, resource), batch_check, filter_visible(list), lifecycle hooks when orgs, namespaces, collections and API keys are created or deleted, and bootstrap. Three implementations:
| Implementation | For |
|---|---|
AllowAll | Development and single-user installs |
Rbac | Built in: roles grant read, write or admin over namespaces and collections |
OpenFga | Fine-grained, delegated grants for multi-org tenancy and BYOC; the default in the managed cloud |
The model. Loams adapts Lakekeeper's modular model (its server, project, warehouse and table become Loams’ platform, org, namespace and collection), copied with attribution under Apache-2.0 and with its notice kept. Loams adds a tenant fence Lakekeeper's model lacks: every grant is effective only when the subject is also a member of the owning tenant, so no tuple, however it was written, can make a subject from another org effective.
Tuple writes through an outbox. Lakekeeper writes tuples before its database commit and deletes them after it, which can orphan tuples when a commit fails. Loams writes each tuple change as a row in the same metastore or control-store transaction as the resource change, and a worker drains the outbox with idempotent writes. A reconcile job repairs any drift. So a resource and its permissions never disagree for longer than the drain.
Reads go through a decision cache in the gateway with a short TTL (at most five seconds), with higher-consistency reads right after a principal's own writes.
Beyond the engine. In the function runtime, per-invocation Biscuit tokens carry only narrowing facts (tenant, function, allowed operations) and OpenFGA remains the authority for every decision on data. In the Loams Commons showcase, one OpenFGA model spans every app (a project fans out to a Loams namespace, repositories, chat channels and error projects), and each app's own permissions are a projection reconciled from it. In the Neon spike, OpenFGA's migrations ran unchanged on Neon, and a check allowed the member and denied the non-member.
Limits
- A network hop per decision, which the cache and batch checks mitigate. Search results filtered per user (
filter_visibleover thousands of candidates) are the expensive case, to be measured before it ships. - One more service with its own database. OpenFGA needs Postgres or MySQL in production. Whether Loams’ and Lakekeeper's model migration managers can share one store cleanly is an open question.
- Tuples are only as correct as their writes. The outbox and the reconcile job exist because dual writes drift.
- Consistency is a choice per read. Default reads favour latency; a grant followed immediately by a check needs the higher-consistency read.
The next post covers the engines Loams plans to run beside it rather than inside it: Sail, RisingWave, Neon, and the MySQL engine behind Loams SQL.