Repository navigation
EN Authorization Tenants and Ownership
English · Türkçe · Languages / Diller
Scope: JSON API Forge 0.5.1 · Source review: 17 September 2026 · Language: English
Authentication establishes who is calling. Authorization determines which action that identity can perform on which data. A Forge principal carries identity kind, subject, roles, permissions and an optional tenant.
{
"roles": {
"reader": {"permissions": ["notes.list", "notes.read"]},
"writer": {"inherits": ["reader"], "permissions": ["notes.create", "notes.update"]}
}
}Version permission names as part of your application contract. notes.* and * are broad grants. Role inheritance is transitive; cycles and unknown roles require validation. Leaving permission empty is not a general way to make an endpoint public. Where supported, use explicit public=true.
tenant_field uses the principal's tenant; owner_field uses its subject. For example:
{
"tenant_field": "tenant_id",
"owner_field": "owner_id",
"owner_actions": ["list", "read", "update", "delete"],
"owner_bypass_permission": "notes.owner.bypass"
}This is a resource fragment, not a complete resource. The corresponding columns must exist. Exclude tenant/owner/soft-delete fields from writable_fields. A client-provided tenant_id in the body is not an authority source.
When a tenant field is configured, a principal missing the required tenant context is denied access. Tenant and owner answer different questions: which customer's data, and which identity's record? They can be enforced together.
Generic CRUD's row policies are not automatically injected into every custom SQL statement or hook. An RPC can apply explicit constraints:
{
"sql": "UPDATE notes SET title = :title WHERE id = :id AND tenant_id = :tenant AND owner_id = :owner",
"mode": "execute",
"params": {
"id": "$body.id", "title": "$body.title",
"tenant": "$principal.tenant_id", "owner": "$principal.subject"
},
"require_rowcount_min": 1,
"require_rowcount_max": 1
}This statement requires a suitable table, input schema and operation permission. Taking the tenant/owner from the request body does not provide the same trust boundary as using the authenticated principal.
- An authorized principal can read/update its own record in its own tenant.
- Another tenant with the same role cannot see that record.
- A different owner within the same tenant receives the intended restriction.
- Bypass works only for explicitly granted identities; cached results do not leak between callers.
Verify actual endpoint behavior, including 404 for invisible rows and 403 for insufficient permissions where applicable. Editor roles use a separate rank/scope model; see the team guide.
This page describes the behavior at the pinned commits below. Recheck these contracts when upgrading.
main/framework/security.pymain/framework/crud.pymain/framework/mongo.pymain/docs/29-Row-Ownership-and-Authorization.md