Authorization Deep Dive (IDOR / BOLA / BFLA)
Authentication answers: âWho are you?â
Authorization answers: âAre you allowed to do this to this data?â
In real incidents: IDORs happen when teams trust UI flows. The server needs to re-check ownership every time, even if it feels repetitive.
Why authorization bugs happen (deep reason)
- Developers confuse UI rules with backend rules: âThe button is hiddenâ â âthe action is blockedâ.
- AuthZ is scattered: some endpoints check ownership, others forget, especially new features.
- Object relationships are complex: nested objects (org â project â invoice) need consistent policy checks.
- Implicit trust in identifiers: assuming IDs in requests always belong to the caller.
Mental model: âwho â what â which object â policy decisionâ
Every sensitive request must enforce:
- Principal: user/service identity (from server-validated session/token)
- Action: read/create/update/delete/approve/export
- Resource: the object being accessed (order #123, invoice #9)
- Policy: rules (owner can read, admin can export, support can view masked)
IDOR / BOLA (Broken Object Level Authorization)
What it is
When the server uses an object identifier from the request (path/query/body) but does not verify the caller is allowed to access that object.
Vulnerable pattern (example)
// Node/Express (example)
app.get("/api/orders/:id", async (req, res) => {
const order = await db.orders.findById(req.params.id);
// â Missing ownership check
res.json(order);
}); Secure pattern
// â
Enforce ownership in the query (best)
app.get("/api/orders/:id", async (req, res) => {
const order = await db.orders.findOne({ id: req.params.id, ownerId: req.user.id });
if (!order) return res.status(404).json({ error: "Not found" });
res.json(order);
}); Testing methodology (systematic)
- Create two users: A and B.
- Let A create an object (order, ticket, document). Capture the request/response.
- Replay the same request as B using Aâs object ID.
- Confirm not just the response, but any state change side effects.
- Repeat for nested objects:
/orgs/{orgId}/projects/{projectId}/invoices/{invoiceId}
Fix recommendations (experienced answer)
- Centralize authorization policies (RBAC/ABAC).
- Enforce ownership checks at the data access layer (query filters / row-level security).
- Add automated tests for authZ matrix (role Ă endpoint Ă object).
BFLA (Broken Function Level Authorization)
What it is
Calling privileged actions (approve, export, admin changes) without role enforcement.
Vulnerable pattern
// â Any authenticated user can approve refunds
app.post("/api/refunds/:id/approve", async (req, res) => {
await refunds.approve(req.params.id);
res.json({ ok: true });
}); Secure pattern
// â
Explicit policy check
app.post("/api/refunds/:id/approve", requireRole("finance_admin"), async (req, res) => {
await refunds.approve(req.params.id);
res.json({ ok: true });
}); Testing methodology
- Inventory privileged endpoints (admin routes, exports, approvals, role management).
- Try calling them with low-priv tokens (user, support).
- Check if the endpoint exists but UI hides it.
Fix
- Use a centralized authorization middleware/policy engine.
- Prefer deny-by-default; explicitly allow roles per action.
- Audit logs: who approved what, when, and from where.
Preventing regressions
- Add integration tests that assert forbidden actions return
403. - Use âauthorization matrixâ test generation (role Ă endpoint).
- Code review rule: any endpoint touching data must show a policy check or constrained query.
Interview-ready answers (60-second + 2-minute)
60-second answer
Authorization is the server deciding who can do what to which resource. Bugs happen when APIs trust user-provided identifiers (IDOR/BOLA) or forget role checks on privileged actions (BFLA). The fix is consistent policy enforcement: query-level ownership checks, centralized authorization middleware/policies, deny-by-default, and regression tests for the roleĂendpoint matrix.
2-minute answer
I think of AuthZ as a policy decision applied to every request that touches data. The common failure mode is âUI rulesâ (hidden buttons) being mistaken for server rules. For object-level authorization, I enforce access at the data layer (e.g., WHERE id=? AND ownerId=?) so thereâs no âfetch-then-checkâ mistake. For function-level authorization, I gate privileged routes (approve/export/admin actions) with explicit policy checks. I also cover tricky cases like nested resources (orgâprojectâinvoice), indirect references (shared links), multi-tenant scoping, and bulk endpoints. Finally I prevent regressions with automated tests for forbidden actions (403), policy centralization, and code review rules.
How authorization exploitation progresses (attacker mindset)
This is a real-world process explanation (no copy/paste exploit steps). Attackers donât start with âdump dataâ â they start by proving inconsistent policy enforcement.
Phase 1: Inventory & classify endpoints
- Object access: âget/update/delete document/order/ticket by IDâ.
- Privileged actions: approvals, exports, role changes, admin switches.
- Bulk endpoints: search, list, batch update, âinclude=detailsâ.
Phase 2: Prove policy inconsistency
- Compare the same endpoint under two identities or two roles.
- Look for endpoints that return 200 where a sibling endpoint returns 403.
- Check âread vs writeâ paths separately â write paths are often less tested.
Phase 3: Expand scope via relationships
- Test nested objects (org/project/invoice) and indirect references (attachments, comments, exports).
- Test âchild objectsâ where the server validates child ID but forgets to validate parent ownership.
Phase 4: Look for high-impact actions
- BOLA: access to another userâs resources (data exposure).
- BFLA: ability to trigger privileged actions (fraud/approval/export/role).
- Tenant escape: crossing organization boundaries in multi-tenant systems.
Tricky edge cases & bypass patterns (what attackers look for)
- Multi-tenant scoping bugs: object checks exist, but tenant boundary is missing (orgId not enforced).
- Indirect references: attachments, exports, âshare linksâ, background jobs that fetch by ID without policy checks.
- Bulk endpoints: list/search/export endpoints returning data outside scope.
- Filter-based leaks: server validates access on detail endpoint but not on search filters.
- âIncludeâ expansion:
?include=owner,notes,internalreturning sensitive fields without re-checking policy. - State transitions: endpoints that change status (approve/close/refund) without role checks.
- Concurrency: TOCTOU where access is checked, then resource is changed between check and use.
Safe validation workflow (defensive verification)
- Baseline: capture the request for a resource owned by User A (or Role Admin).
- Cross-identity test: repeat as User B (or lower role) using the same resource ID.
- Confirm scope: verify not only response codes, but side effects (status changes, notifications, audit logs).
- Relationship checks: repeat for nested resources (org â project â invoice) and indirect assets (attachments/exports).
- Regression test idea: add an automated test asserting forbidden access returns
403.
Defensive patterns & fixes (Node.js)
Pattern 1: Query-level enforcement (best for BOLA)
// â
Ownership enforced in the query
app.get("/api/orders/:id", async (req, res) => {
const order = await db.orders.findOne({ id: req.params.id, ownerId: req.user.id });
if (!order) return res.status(404).json({ error: "Not found" });
res.json(order);
}); Pattern 2: Central policy middleware (best for BFLA)
// â
Explicit policy check for privileged actions
function requireRole(role) {
return (req, res, next) => {
if (!req.user || req.user.role !== role) return res.status(403).json({ error: "Forbidden" });
next();
};
}
app.post("/api/refunds/:id/approve", requireRole("finance_admin"), async (req, res) => {
await refunds.approve(req.params.id);
res.json({ ok: true });
}); Confidence levels (how sure are you?)
- Low: suspicious inconsistency (different behavior across endpoints/roles) but not yet reproducible.
- Medium: reproducible cross-identity access difference with controlled scope (e.g., wrong row/scope).
- High: confirmed unauthorized access or unauthorized privileged action with clear evidence and minimal exposure.
Checklist (quick review)
- Every data-touching endpoint enforces policy (not UI rules).
- Ownership checks happen at the query/data layer where possible.
- Role checks protect approvals/exports/admin actions.
- Nested resources validate parent + child ownership/tenant scope.
- Bulk/search/export endpoints are scoped and tested.
- Automated tests cover role Ă endpoint Ă object matrix.
- Audit logs exist for privileged actions.
Remediation playbook
- Contain: disable/guard the vulnerable endpoint or feature flag it.
- Fix: implement centralized policy checks + query-level ownership filters.
- Scope: search codebase for similar endpoints (same resource type, same pattern).
- Test: add regression tests for forbidden cases (
403). - Monitor: alert on unusual access patterns and privileged action usage.
- Review: enforce code review policy: âshow the authorization decisionâ for every endpoint.
Interview Questions & Answers (Easy â Hard)
Easy
- AuthN vs AuthZ?
A: AuthN is âwho are youâ; AuthZ is âare you allowed to do this to this resource?â. - What is IDOR/BOLA?
A: When the server uses an object ID from the request but doesnât verify the caller is allowed to access that object. - Why is âhiding a buttonâ not a fix?
A: UI is not enforcement. Attackers call APIs directly; the server must enforce policy. - Best primary fix for BOLA?
A: Ownership enforcement at the query/data layer (scope by ownerId/tenantId). - Best primary fix for BFLA?
A: Central policy checks/middleware with deny-by-default. - What HTTP code for forbidden?
A:403for authenticated but not allowed;401when not authenticated.
Medium
- How do you test for BOLA safely?
A: Two identities. Create object as A, replay as B with the same ID, confirm response + side effects. - Where do these bugs hide most?
A: Nested resources, bulk endpoints, exports, attachments, and admin tooling. - Why is âfetch then checkâ risky?
A: Easy to forget checks; may leak fields before check. Query-level enforcement reduces exposure. - How do you handle multi-tenant scope?
A: Every query must include tenant boundary (orgId/tenantId) in addition to ownership/role. - Follow-up: whatâs your prevention strategy?
A: Centralize authZ, add roleĂendpoint tests, and require visible policy checks in code review. - Follow-up: why not rely on frontend checks?
A: Frontend is bypassable. Server must enforce.
Hard
- Scenario: nested route checks child but not parent. Risk?
A: Parent scoping bypass (org/project boundary). Fix by validating both parent and child belong to caller/tenant. - Scenario: export endpoint leaks fields not present in UI. Why common?
A: Export paths are often built quickly and skip field-level policy checks. Fix: field allow-lists and policy per export. - Scenario: batch update accepts list of IDs. Whatâs the pitfall?
A: Partial authorizationâsome IDs may be unauthorized. Fix: authorize each ID or constrain query to authorized set only. - Scenario: support role can âviewâ but not âexportâ. How enforce?
A: Separate actions in policy (view vs export), enforce deny-by-default, audit exports. - Follow-up: what tests do you add after fixing?
A: Integration tests asserting forbidden access is403across roleĂendpointĂresource combinations. - Follow-up: how do you keep policy consistent at scale?
A: Central policy engine, code scanning for raw routes without checks, and shared data-access helpers.