IDOR prevention checklist (authorization): what to review in 30 minutes
securitywebai

IDOR prevention checklist (authorization): what to review in 30 minutes

4 min read

A practical, ops-style authorization checklist to prevent IDOR (broken object-level access control). Focus on where IDs enter, how reads/writes are scoped, consistent deny behavior, and one regression test that prevents reintroducing the bug.

Table of Contents

What should you review to prevent IDOR (broken authorization) in a web app?

Conclusion

In 30 minutes, you can eliminate most IDOR risk by verifying three things:

  1. Where IDs come from (URL, body, query) and assuming they can be tampered with
  2. Read boundaries include owner_id / tenant_id (not only id = :id)
  3. Write boundaries include the same boundary (UPDATE/DELETE scoped by owner/tenant)

Then you lock it with one regression test:

  • “User B cannot read or mutate User A’s object”

If the checklist is clean, invest in RBAC/ABAC later.

Implementation examples may be available on DevSnips.

Explanation

Authentication answers “who are you?” Authorization answers “which object can you access?”

IDOR happens when a valid user can access another user’s resource by changing an ID. AI-assisted development increases the frequency because generated code often:

  • protects routes with auth middleware
  • but still queries WHERE id = :id
  • without enforcing owner/tenant boundaries

UUIDs reduce guessing, not access control. Authorization must be enforced server-side.

Practical Guide

Step 0 (3 min): list the 3 objects that would be fatal to leak

Examples:

  • billing: invoices, subscriptions
  • business data: projects, documents, tickets
  • admin: members, roles, invites

Decision rule:

  • If leaking it causes a reportable incident, it’s in scope.

Step 1 (5 min): find every endpoint/action that accepts an ID

Look for:

  • GET /api/<object>/:id
  • PATCH /api/<object>/:id
  • DELETE /api/<object>/:id
  • POST /api/<object>/update (id in body)

Also search for IDs in payloads:

  • id, userId, orgId, tenantId, projectId

Decision rule:

  • If the client can send it, it must be validated and bounded.

Step 2 (8 min): verify read queries always enforce a boundary

Your read query must include an owner/tenant scope.

  • single-tenant: WHERE owner_id = current_user_id AND id = :id
  • multi-tenant: WHERE tenant_id = current_tenant_id AND id = :id

Anti-patterns:

  • findById(id) only
  • WHERE id = :id only
  • boundary derived from user input (tenant_id = body.tenantId)

Step 3 (8 min): verify writes enforce the same boundary

Reads are not enough. Writes must be scoped too.

  • UPDATE/DELETE WHERE includes owner_id / tenant_id
  • treat “0 rows updated” as deny (403/404) per policy

Anti-patterns:

  • read is checked, write is WHERE id = :id
  • two-step checks outside a transaction (TOCTOU)

Step 4 (4 min): list/search endpoints are common leaks

Examples:

  • GET /api/<object>?q=...
  • GET /api/<object>/recent

Checklist:

  • does every query include tenant/owner scoping?
  • do you have a shared query helper or default scope?

Step 5 (2 min): add one negative boundary test

Minimum:

  • User B cannot read User A’s object
  • User B cannot mutate User A’s object

Decision rule:

  • If you can’t write this test, your boundary is not centralized enough.

Pitfalls

  • “We hide it in the UI” (not a control)
  • trusting client-provided tenantId / orgId
  • inconsistent deny behavior (some routes 403, others 200)
  • background jobs/webhooks mutate by ID without the same boundary
  • logging raw payloads and leaking PII/secrets

Checklist

  • [ ] Top 3 high-impact object types are listed
  • [ ] All ID entry points are inventoried (URL/body/query)
  • [ ] Read queries include owner/tenant boundaries
  • [ ] Write queries include the same boundaries
  • [ ] Client-provided boundary fields are never trusted (tenantId, orgId)
  • [ ] Deny semantics are standardized (403 vs 404)
  • [ ] List/search endpoints are scoped by owner/tenant
  • [ ] A shared helper exists for boundary-scoped queries
  • [ ] Negative boundary tests exist (B cannot access A)
  • [ ] Denial logging exists without leaking PII/secrets
  • [ ] AI-generated code touching authz requires explicit review gates

FAQ

Q1. Is route-level auth middleware enough?

No. Middleware answers “is the user logged in?”. IDOR is about “which object can they access?”, which must be enforced in the handler/data layer.

Q2. Should I return 403 or 404 for unauthorized access?

Either can be correct. Pick one based on your threat model and auditing needs, then apply it consistently across the app.

Q3. What’s the fastest way to prevent regressions?

One regression test per object type: “User B cannot access User A’s object”. This prevents reintroducing IDOR during refactors or AI-assisted changes.

References

Popular

  1. 1Permit2 explained (Web3): why approvals changed and how to use it safely (checklist)
  2. 2Read wallet signing screens (Web3): a 30-second checklist to avoid permission traps
  3. 3Spec-to-implementation prompt template (AI development): how to stop the model from guessing
  4. 4Revoke token approvals on EVM: how to audit allowances safely (checklist)
  5. 5Clarifying questions checklist (AI development): what to ask before you let an LLM build

Related Articles