Intermediate May 3, 2026 · 2 min read

Loom's permission model is deliberately small: a Thread declares which roles can do which CRUD actions, and the runtime enforces those declarations on every request.

Declaring permissions

Each Thread has a permissions: block:

permissions:
  - role: Admin
    can: all
  - role: Sales
    can: [read, create, update]
  - role: Public
    can: [read]

can: accepts either the literal all (every CRUD action) or a list of [read, create, update, delete]. Anything else is rejected by loom check.

Roles come from the JWT

Loom's runtime expects a JWT in the Authorization: Bearer … header. The token's roles claim is parsed as a string array; that becomes the user's role set for the request.

{
  "sub": "5e2b7d4f-8c1a-4b6f-9c3d-2a1b3c4d5e6f",
  "email": "alice@example.com",
  "roles": ["Admin"],
  "iat": 1714670400,
  "exp": 1714756800
}

How tokens get issued is up to you. Loom v0.1.0 does not ship a /auth/login endpoint; you write it yourself or integrate an external IdP.

The Public role

Public is special: when a Thread grants the Public role any action, every caller — authenticated or not, regardless of their actual roles — can perform it.

permissions:
  - role: Public
    can: [read]
  - role: Admin
    can: all

Anyone can list and read. Only Admin can create/update/delete. This is the standard "public catalogue, private mutations" pattern.

The behaviour is intentionally locked by a test (TestCan_PublicGrantAppliesToEveryone) — it can't drift accidentally.

Deny by default

If a Thread has no permissions block at all, nobody can touch it through the API. This is the safe default. Add at least one permission entry per Thread you want exposed.

What gets checked, when

Step Check
HTTP middleware JWT signature + expiry
Per-handler entry Can(user, thread, action) for the requested action
If denied 403 Forbidden returned before any SQL runs

Permission checks happen before the handler builds SQL, so a denied request never reaches the database — no risk of timing attacks based on row existence.

Field-level permissions

Loom v0.1.0 does not support field-level access control. If you need "users can read their own email but not anyone else's", that's application-level filtering in your handler logic; it isn't expressible in the Thread declaration.

A possible v0.2.0 feature is per-field role gating; if you need it, file an issue.

Was this article helpful?