Mesrai
Back to blog
// essaySecurity

Authorization Bypass via Missing req.user.id Check

Real PR catch: GET /documents/:id checks auth but not ownership. Every doc readable by every user. CWE-862 walkthrough with predicate fix.

Mesrai TeamJuly 25, 20268 min read

A GET endpoint that returns a document by id. Auth middleware in place. The author thought the auth check was enough — and it would be, if every authenticated user could read every document. They could not. Mesrai flagged the missing ownership check.

The vulnerable diff

tsapi/documents.ts
// api/documents.ts
app.get("/documents/:id", requireAuth, async (req, res) => {
  // BUG: only auth check, no ownership predicate
  const doc = await db.documents.findUnique({
    where: { id: req.params.id },
  });
  if (!doc) return res.status(404).end();
  res.json(doc);
});

What is wrong

Authentication is 'who is the user'. Authorization is 'is this user allowed to do this action on this resource'. The two are conflated in many codebases because authentication is centralized in middleware while authorization tends to live per-handler — and a handler that forgets the second check ships looking fine. CWE-862 (Missing Authorization) is OWASP A01 — the single most common class of web vulnerability in 2024-2025 industry reports.

The attack

Attacker logs in as a normal user, then enumerates document ids:

bashexploit
# Logged in as normal user. Enumerate ids.
for id in $(seq 1 10000); do
  curl -s -H "Cookie: sid=$MY_SID" \
    https://api.example.com/documents/$id | \
    jq -r 'select(.ownerId != $ENV.MY_USER_ID)'
done
# Returns every document the attacker should NOT be able to see.

Variants: sequential integer ids make enumeration trivial. Even UUIDs leak through analytics, support tickets, public links, or a single leaked legit URL.

Mesrai's review comment

textreview-comment.txt
mesraipilot · Bot · reviewed 1 min ago

[mesrai] [code-review] [Security] [CWE-862] [critical]

GET /documents/:id checks auth but not ownership. Any logged-in user
can request any document by id and read it. This is the canonical
IDOR / Broken Object Level Authorization pattern, OWASP API #1.

Fix: include the ownership predicate in the query so 'not found' and
'not yours' return the same 404 — and never leak existence:

  const doc = await db.documents.findFirst({
    where: { id: req.params.id, ownerId: req.user.id },
  });
  if (!doc) return res.status(404).end();

For documents with sharing (multi-user access), check membership:

  where: {
    id: req.params.id,
    OR: [
      { ownerId: req.user.id },
      { sharedWith: { some: { userId: req.user.id } } },
    ],
  }

Reference: OWASP Top 10 A01:2021 — Broken Access Control, CWE-862

The fix

tsapi/documents.ts (fixed)
// api/documents.ts — fixed
app.get("/documents/:id", requireAuth, async (req, res) => {
  const doc = await db.documents.findFirst({
    where: { id: req.params.id, ownerId: req.user.id },
  });
  if (!doc) return res.status(404).end();
  res.json(doc);
});

Two principles: include the ownership predicate in the SQL/query, so the database does the access check (cannot be bypassed by code that forgets a follow-up guard); and return the same 404 for 'not found' and 'not yours' so the response cannot be used to enumerate the existence of someone else's documents.

Why human review missed it

Missing authorization is the hardest class to catch in code review because the code looks correct at a glance — there is an auth check (just the wrong one), the response shape is right, the tests pass. The bug only shows up when you ask 'whose id is this and is the requester allowed to see it'. Mesrai's rule pack flags every handler that takes a resource id from the request and does not include an ownership predicate in the query or a guard after the fetch.

Related rules + further reading

Mesrai rule pack: security/missing-authorization-on-resource — flags handlers fetching by request-id without an ownership predicate.

OWASP: Top 10 A01:2021 — Broken Access Control. The most common web vulnerability class.

CWE-862 — Missing Authorization.

Takeaway

Every handler that reads a resource id from the request must include the ownership check in the query. Auth is necessary, authorization is sufficient. Mesrai flags every gap.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.