Access Control at the Data Layer
Access control at the data layer is authorisation enforced where records are read or written, so every query is constrained by the authenticated user, tenant, or role. It prevents a valid session, hijacked session, or buggy code path from becoming permission to fetch or mutate records that belong to someone else.
The need comes from how web identity is usually carried across requests. After login, the server or a signed cookie remembers a session identifier that maps to a user. Later requests present that cookie and are treated as that user. If an attacker steals the token, or a handler forgets a tenant or owner filter, the application may make perfectly valid database calls on behalf of the wrong subject.
Concretely, data-layer access control makes the subject part of the read or write. A request for an invoice is not just “find this invoice by id”; it is “find this invoice by id and by the current user or tenant”. In stronger forms, the database itself enforces this with row policies using a per-request setting, so a query missing the tenant predicate is rejected or returns no rows.
The trade-off is complexity and friction. Every path to the data must carry identity and scope correctly, including background jobs, migrations, support tooling, and administrative workflows. Database-enforced policies can make debugging harder because a query that looks valid may silently see a filtered view of the world. The honest design answer depends on the risk: shared-tenant systems usually need stricter enforcement than isolated deployments.
Engineers meet this in session middleware, API handlers, ORMs, database policies, and infrastructure authorisers. Rails and Django establish identity from cookies; application queries must then bind that identity to records. PostgreSQL Row-Level Security can enforce tenant boundaries inside the database. Kubernetes RBAC applies the same idea to API resources: an authenticated subject is checked against permitted actions before secrets or other objects are returned.
Common questions
- Is checking permissions in the controller enough?
- Often not. Controller checks help, but they are easy to bypass through another route, a new API endpoint, an export job, or a maintenance script. Data-layer checks make the dangerous operation itself subject-aware, so the query or write cannot succeed merely because one caller forgot to apply the right filter.
- How does session hijacking relate to data-layer authorisation?
- A hijacked session makes the attacker look like the legitimate user for as long as the token is accepted. Data-layer authorisation cannot tell that the browser is malicious, but it can still limit the blast radius to records that the impersonated user is allowed to access, rather than every record reachable by a careless query.
- Does Row-Level Security replace application-level permissions?
- No. It is a backstop and enforcement point, not a full product permissions model by itself. The application still decides flows, roles, validation, and user experience. Row-Level Security is valuable because it keeps core ownership or tenant rules close to the records, where accidental broad queries can be contained.
- What is commonly misunderstood about tenant filters?
- A tenant filter is not just a convenience condition for list pages. In a multi-tenant system it is part of the security boundary. Fetching a row by object identifier alone is unsafe if identifiers can be guessed, leaked, logged, or copied from another context. The tenant or owner constraint must travel with the access.