Code review example: IDOR: any logged-in user can read every other user's order in Node

Retired from the DiffDojo practice pool in October 2026; the review below is the canonical answer. An AI wrote this pull request for the task underneath. Read the diff and decide what you would say before you scroll to the review.

Node · Security · intermediate

The task

Add an order details endpoint

  1. Add GET /orders/:id that returns a single order as JSON.
  2. The route is mounted behind the existing requireAuth middleware, which sets req.userId from the session.
  3. Return 404 when the order does not exist.

Adds the order details endpoint. Pulls the order by its id from the database and returns it, with a 404 when there's no such row. requireAuth already guards the route, so only logged-in users can reach it. Verified against seeded orders and the 404 path.

This is the customer-facing store API on the public internet; order ids are sequential integers visible in URLs and emails.

The diff

src/routes/orders.js+7
1+ router.get('/orders/:id', requireAuth, async (req, res) => {
2+ const order = await db.orders.findById(req.params.id);
3+ if (!order) {
4+ return res.status(404).json({ error: 'Order not found' });
5+ }
6+ return res.json(order);
7+ });

The review

Issue 1: IDOR: any logged-in user can read every other user's order

src/routes/orders.js, lines 2, 3, 4, 5, 6

  1. requireAuth proves login, not ownership. The handler fetches purely by the URL id and never checks the order belongs to req.userId.
  2. The ids are guessable. Order ids are sequential and visible to customers, so any authenticated user can walk /orders/1, /orders/2, ... and read every other user's serialized order data.
  3. Authentication is not authorization, the route is guarded, but only against anonymous callers.

The fix: after loading the order, verify order.userId === req.userId; on mismatch return 404 to conceal that the id exists (a 403 is also valid but reveals the resource exists). Ideally scope the query itself to the user.

The fix

router.get('/orders/:id', requireAuth, async (req, res) => {
  const order = await db.orders.findById(req.params.id);
  if (!order || order.userId !== req.userId) {
    return res.status(404).json({ error: 'Order not found' });
  }
  return res.json(order);
});

Why it matters

An :id taken from the URL and used straight in a query → ask: "who owns this row, and where do I check?"

Real-world stakes: IDOR is one of the most-reported real-world API vulnerabilities, no exotic payload, just changing a number in a URL. A logged-in customer opens dev tools, sees GET /orders/48213, and tries /orders/48212: someone else's shipping address, items, and total come back. Scripted, it dumps the whole orders table through a single 'authenticated' endpoint, and every entry looks like a normal request in the logs.

Read more: IDOR insecure direct object reference, broken object level authorization OWASP API, express check resource ownership req.user, authentication vs authorization

The verdict

Request changes. IDOR: any logged-in user can read every other user's order

Try a live one

Node PRs you have not seen the answer to.

Leave inline comments and a verdict, then see what you caught. Free, no signup.

Review a Node PR →

How to review Node code · All code review examples