Insecure direct object reference (IDOR), also called broken object-level authorization (BOLA), occurs when an API identifies a resource but fails to verify that the requester may access it. A valid login proves who the requester is; it does not prove ownership of every requested object. section 4.2 section 2.3.2
The failure appears when user A can read or modify a resource belonging only to user B. Because APIs commonly expose identifiers in paths and requests, an attacker may try other valid identifiers and obtain private data or perform unauthorized operations. section 4.11 section 4.2
An order endpoint that trusts the identifier
Suppose an e-commerce API exposes GET /orders/{order_id}. Authentication validates the bearer token, but the database query filters only by order ID. A requester with a valid token can ask for another customer’s order and receive its details, including sensitive address or payment information. The same pattern can affect updates: on a blogging platform, user B’s post with ID 1 should be editable only by user B, but user A can edit it when the endpoint checks the ID without checking the author. section 4.3 section 4.2
Listing 4.1 Implementation of an orders resource endpoint vulnerable to BOLA
# file: ch04/bola.py
@server.get("/orders/{order_id}") #1
def get_order_details(
order_id: int, #2
user_claims: UserClaims = Depends(validate_access) #3
):
with session_maker() as session: #4
order = session.scalar( #5
select(OrderModel).where(OrderModel.id == order_id)
)
if order is None: #6
raise HTTPException(
status_code=404,
detail=f"Order with ID {order_id} not found."
)
return { #7
"id": order.id,
"product": order.product,
"quantity": order.quantity,
}
Inspect the vulnerable orders endpoint: token validation authenticates the caller, while the query retrieves any matching order ID without comparing the order owner with the caller. section 4.3
Fix BOLA by enforcing object-level authorization as part of every resource query. In the order example, require both the requested order ID and the authenticated user’s identity to match. Return the resource only when both conditions hold; otherwise, do not disclose another user’s object. section 4.3 section 4.11
Listing 4.2 Fixing BOLA vulnerabilities by checking a user’s right to access a resource
# file: ch04/bola.py
@server.get("/orders/{order_id}")
def get_order_details(
order_id: int,
user_claims: UserClaims = Depends(validate_access)
):
with session_maker() as session:
order = session.scalar(
select(OrderModel).where(
OrderModel.id == order_id,
OrderModel.user == user_claims.sub, #1
)
)
if order is None:
raise HTTPException(
status_code=404,
detail=f"Order with ID {order_id} not found."
)
return {
"id": order.id,
"product": order.product,
"quantity": order.quantity,
}
Compare the corrected query with the vulnerable version: it adds the authenticated subject as an ownership condition instead of trusting the object ID alone. section 4.3
Turn the rule into repeatable checks:
- Create two users and resources associated with each user; verify that user A cannot access or modify user B’s resources. section 2.3.2
- Test every endpoint that reads or changes an object, including orders, registrations, posts, payments, and patient data. section 12.4 section 4.11
- Automate the negative case: authenticate as one user, request another user’s object identifier, and assert a forbidden response such as HTTP 403. section 12.4
- Repeat these tests after future changes and fix endpoints incrementally when a complete application-wide suite is impractical. section 2.3.2
Listing 12.3 Implementing a BOLA test
# file: ch12/tests.py
[...]
from models import Course, CourseRegistration, Student
[...]
client = TestClient(server)
[...]
def test_bola():
with session_maker() as session:
student = session.scalar(select(Student))
registration = session.scalar(
select(CourseRegistration).where(
CourseRegistration.student_id != student.id
)
)
token = create_access_token(sub=str(student.id)) #1
response = client.get(
f"/registrations/{registration.id}",
headers={ #2
"Authorization": f"Bearer {token}"
}
)
assert response.status_code == 403
Use the test pattern as a template: create a token for one student, request a registration belonging to another, and expect HTTP 403. section 12.4

