Security Overview
Rows, columns, fields, and who — the four questions the security pillars answer, and which page owns each one.
Quickback's security is not one mechanism. It is four different questions, each answered by a different pillar, each enforced at a different point in the request. This page is the map; every mechanism has its own page.
The four questions
| The question | Pillar | Where it runs |
|---|---|---|
| Which rows may this caller ever see? | Firewall | A WHERE clause added to every query |
| Who may perform this operation? | Access | Middleware, before the handler |
| Which columns come back on a read? | Views | A read projection |
| Which fields may be written? | Guards | Write validation, before the DB |
| What is redacted in the response? | Masking | A response transform |
| How often may they ask? | Rate limits | Edge, before routing |
Views and Guards are documented under Reads and Writes rather than under Security, because that is where you are when you need them: a view is a shape you return, a guard is a rule about a write. They are security mechanisms either way.
Firewall — which rows
The firewall is tenant isolation. It rewrites every query so a caller can only ever address rows inside their own scope. It is not a permission check that can be granted away — it is the boundary of the addressable data set.
Isolation columns are auto-detected from the schema:
organizationId— plus five other accepted spellings, see below — scoped toctx.activeOrgIdteamId— scoped toctx.activeTeamIdownerId— scoped toctx.userId
The owner-column rule
This is the rule that is most often stated backwards, so state it once and link it:
ownerIdis auto-detected as the owner column: the compiler firewalls reads withWHERE ownerId = ctx.userIdand auto-stamps the column on insert.
userIdis not auto-detected on feature tables. It is a Better Auth convention on Better Auth's own tables (session,account,member,passkey), and on a feature table it usually means a user this row references — a member, a contact, an invitee — rather than the row's owner.If
userIdis the only candidate column on the table, the compiler raises an error. Alongside a detected isolation column it raises a warning.Two escapes: rename the column to
ownerId, or declarefirewall: { owner: { column: "userId" } }explicitly.
Six spellings of the organization column
Organization auto-detection accepts six column names, American and British, in this precedence order. The first one present in the schema source wins:
organizationId
organisationId
orgId
organization
organisation
orgAccess — who
Access is the role check. It runs as middleware, before your handler, per operation —
read, create, update, delete, and each action independently.
Two things it is not:
- It is not row filtering. A caller who passes the access check still only sees the rows the firewall admits.
- It is not a substitute for the firewall. A scoped-role gate is an access decision; the row-scope-column boundary is a firewall decision. Mixing them produces policies that read as security but enforce nothing.
Beyond plain role lists, Access carries record conditions, context substitution,
relationship-based roles, and the SESSION / SCOPED / AUTHENTICATED pseudo-role
tiers. See Access.
Columns and fields
Reads — Views are named column-level projections with their own access control. A view is how you return a narrower shape to a broader audience.
Writes — Guards declare which fields can be set on create
(createable), modified on update (updatable), changed only through an action
(protected), or never changed after creation (immutable). A protected field is
omitted from the generated request schemas entirely, so a client cannot even send it.
Redaction — Masking redacts values in the response based on the caller's role. Use it for data the caller may legitimately address but should not read in full.
Advanced authorization
When roles are not enough — when access depends on a relationship between the caller and the row — the model extends:
- Permissions — named capabilities instead of raw role strings
- Scopes — sessionless scope-token principals
- Arrows — relationship traversal
- FGA — fine-grained authorization tuples
- Diagnostics — what the compiler decided, and why
Encryption
Encryption and the sealed protocol cover data at rest. They are orthogonal to the pillars above: encryption governs what the stored bytes are, the pillars govern who reaches them.
Where this is enforced
Everything on this page is a compile-time decision. The pillars are declared in your
feature files, checked for coherence when you run quickback compile, and emitted as
generated middleware and query builders. There is no runtime policy engine to
misconfigure and no rules to keep in sync by hand — if a policy is incoherent, the
compile fails.