Engineer's toolkit
Authorization models and IDOR
Compare RBAC, ABAC and relationship-based access, and learn how insecure direct object references let users read each other's data.
Authentication answers who you are. Authorization answers what you may do to a specific thing. Most data breaches in web apps are authorization failures, not broken logins. The user was properly authenticated, and the server never checked whether they owned the record they asked for.
Three models cover most designs. Each one answers a different question.
Three models
| Model | Question it answers | Example |
|---|---|---|
| RBAC (role-based) | Does this user's role allow the action? | Admins can refund; support agents can't |
| ABAC (attribute-based) | Do attributes of the user, resource and context match a rule? | Edit only own documents, during business hours, from a corporate network |
| ReBAC (relationship-based) | Is there a path of relationships from this user to this resource? | A user can view a file because they are in a group that the folder is shared with |
RBAC is simple and auditable, and it's the right start for most apps. It breaks down when rules depend on the object. "Admins can edit any post" is fine. "Users can edit posts in their team" needs the post's team, which a role alone doesn't know.
ABAC handles that with attributes. It's flexible, but rules spread across code and can be hard to audit.
ReBAC models access as a graph. Google's Zanzibar paper (2019) describes the system behind Google Drive and other products. Open-source descendants include OpenFGA, SpiceDB and Ory Keto. Use ReBAC when sharing and nesting are central, such as documents, folders and organizations.
Insecure direct object references
IDOR is the failure where the app trusts an ID in the request without checking ownership. The URL names the object, and the server returns it.
GET /api/invoices/9001 → 200 OK, the caller's own invoice
GET /api/invoices/9002 → 200 OK, another customer's invoiceNothing in the first request was wrong. The second works because the handler checks only that the user is logged in. Sequential IDs make it worse, since an attacker just counts up. But even UUIDs don't fix IDOR. They only make guessing slower. The real fix is the check.
The rule is simple: every handler that loads an object must check the caller's access to that object, not only the caller's login.
// Vulnerable: authenticated, but not authorized
const invoice = await db.invoice.findUnique({ where: { id } })
// Safer: the query itself enforces ownership or the org scope
const invoice = await db.invoice.findFirst({
where: { id, orgId: session.orgId },
})
if (!invoice) return notFound() // 404 for both missing and forbiddenReturning 404 for "exists but you can't see it" stops attackers from probing which IDs exist.
Tip
Put the scope in the query, such as orgId, and not only in an if after it. Then a forgotten check can't return someone else's row.
Where to enforce it
Check on the server, on every request. Hidden buttons in the UI are not access control. Keep the policy in one place, so the API, background jobs and admin tools all agree. For multi-tenant systems, the tenant scope belongs in the data access layer. See multi-tenancy for how that scoping is designed.
Common mistakes
- Checking that a user is logged in but not that they own the object.
- Trusting client-sent fields such as
roleororgIdin the request body. - Checking access in one endpoint and forgetting a second endpoint for the same data, such as an export or a search.
- Hiding a button in the UI and assuming the API is protected.
Check yourself
- Why is a UUID not a fix for IDOR? Answer: it only makes guessing harder. Anyone who learns the ID, from a link or a log, can still read the object unless the server checks ownership.
- Why return 404 instead of 403 for an object the user can't see? Answer: 403 confirms the object exists. 404 reveals nothing about other tenants' data.
Key takeaways
- Authentication proves identity. Authorization decides access to each object.
- Choose RBAC for simple roles, ABAC for attribute rules, and ReBAC for sharing graphs.
- Check object ownership on every read and write, and scope queries by tenant.
- Enforce on the server, in one policy layer, and never trust client-sent roles.
Further learning
The OWASP Authorization Cheat Sheet is a short, practical list of the checks to add to every endpoint.
OWASP Authorization Cheat SheetWebsite with the collection of all the cheat sheets of the project.cheatsheetseries.owasp.orgNext, see MFA and passkeys for the second factor that keeps a stolen password from being enough.
Related
Authentication and authorization
Authentication proves who you are, authorization decides what you can do, and service-to-service calls need their own identity. The map for the rest of this topic.
OAuth and OpenID Connect
OAuth 2.0 delegates access to a third-party app. OpenID Connect adds a login identity on top. The authorization code flow with PKCE is the safe default.
Multi-tenant SaaS architecture
Designing one system for many customers, covering isolation models, tenant scoping, noisy neighbors, tenant-aware sharding and per-tenant cost.