← Stackzilla Blog
Authentication Is Not Authorization: Closing the Tenant Boundary
Published November 1, 2026
· 3 min read
· security, authorization, SaaS, backend
A signed-in user is not automatically allowed to access every record. Build authorization around the resource and the account that owns it.
A valid login proves something about a caller's identity. It does not prove that the caller may read an invoice, edit a project, or invite another administrator. Applications that blur these responsibilities can expose data even when their login system works perfectly.
In a multi-tenant product, the account boundary is especially important. Two users may have the same role name while belonging to entirely different organizations. A role check without an ownership check can still grant access to the wrong customer's information.
## Check the action on the actual resource
For each operation, identify the actor, action, resource, and relevant account. Then evaluate whether that actor has permission for that action in that context.
Do not trust an account identifier supplied in a request body merely because the user is authenticated. Derive or validate account membership on the server. Fetching a project by its identifier should include the authorized account scope, not fetch it globally and hope the interface hid the wrong links.
Unpredictable identifiers help reduce casual guessing, but they are not an authorization mechanism. A leaked URL, browser history entry, or shared screenshot can reveal one.
## Apply the rule to every path
The main detail endpoint is only one access path. Search, exports, bulk actions, file downloads, background jobs, and websocket subscriptions need equivalent checks. A secure edit screen does not help if a CSV export includes every tenant.
Centralize reusable policy logic, but keep the resource context visible. A generic `isAdmin` helper may be too vague if administrators have authority only within one account.
Consider database row-level security as defense in depth where appropriate. It still requires correct policy design and safe connection handling. A pooled connection must not accidentally carry one request's tenant context into another request.
## Test relationships, not just roles
Build a test matrix with two accounts and several roles. Verify that an owner can perform permitted actions in their own account and cannot perform the same actions against another account's resources.
Include users whose membership was revoked, records moved between accounts, and cached responses. Authorization decisions and personalized caches can become stale. Particularly sensitive operations may need a fresh permission check.
Choose error responses deliberately. Revealing whether a private record exists can itself disclose information. Log denied actions with useful identifiers while avoiding confidential record content.
Authorization is an ongoing relationship between a person and a resource. Treating it as one checkbox set during login misses how those relationships change over time. The safest default is to deny access unless the server can establish the permission needed for this specific action.
## Further reading
- [OWASP: Authorization cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html)
- [PostgreSQL: Row security policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
Read the full article on Stackzilla →