Insecure Direct Object Reference (IDOR) — what it is and how to prevent it
IDOR is the classic name for an authorization bug where a user can change an ID in a URL and access data they should not see. It is BOLA's older cousin and still ships in half of all new apps.
IDOR is a twenty-year-old vulnerability class that is shipping in more new apps today than ever before — because AI coding assistants produce it by default. This guide explains what IDOR is, why it keeps reappearing, and how to stop it with a combination of database-level and API-level controls.
What it is
An Insecure Direct Object Reference occurs when an application exposes a reference to an internal object (a database row, a file path, a tenant identifier) and relies on obscurity or client-side controls to prevent unauthorized access. When the reference is sent over the wire and the server does not re-authorize the access, the reference can be substituted by an attacker.
Vulnerable example
// Vulnerable: uses client-supplied user_id
export async function GET(req: Request) {
const url = new URL(req.url);
const userId = url.searchParams.get("user_id"); // <-- attacker-controlled
const user = await db.user.findUnique({ where: { id: userId! } });
return Response.json(user);
}Fixed example
// Fixed: use session-derived identity, ignore client-supplied IDs for "me" endpoints
import { auth } from "@/lib/auth";
export async function GET() {
const session = await auth();
if (!session?.user?.id) return new Response(null, { status: 401 });
const user = await db.user.findUnique({ where: { id: session.user.id } });
return Response.json(user);
}How Securie catches it
apps/web/app/api/orders/[id]/route.ts:21Insecure Direct Object Reference (IDOR)
Securie's IDOR specialist walks every route and identifies parameters that control access to persisted objects. Then the verification sandbox submits requests with substituted identifiers from fixtures representing different users. A confirmed data leak becomes a finding; the patch rewrites the route to use session-derived identity where appropriate.
// Fixed: use session-derived identity, ignore client-supplied IDs for "me" endpoints
import { auth } from "@/lib/auth";
export async function GET() {
const session = await auth();
if (!session?.user?.id) return new Response(null, { status: 401 });
const user = await db.user.findUnique({ where: { id: session.user.id } });
return Response.json(user);
}Checklist
- Never trust client-supplied IDs as the sole basis for authorization
- Prefer session-derived identity for 'me' endpoints (`/api/me/orders`, not `/api/orders?user=X`)
- Pair API-layer authz with database RLS — defense in depth
- Normalize error responses — do not leak existence through 404 vs 403 differences
- Audit-log every access to sensitive objects
FAQ
Is IDOR the same as BOLA?
They are the same class of bug with different names. OWASP API Security adopted BOLA; OWASP Web Top 10 used IDOR. Securie treats them as one specialist category.
Related guides
BOLA is the top item on the OWASP API Security Top 10 for a reason — every AI coding assistant introduces it by default. Learn what it looks like in Next.js, how to exploit it, and how to fix it.
OWASP's LLM Top 10 is the canonical taxonomy for AI-feature security. Different from the regular OWASP Top 10, it covers the bug classes that only exist when you ship LLMs in production — prompt injection, insecure output handling, training-data poisoning, model DoS, supply-chain risks, and more. Here's each category with a real-world example and the Securie specialist that catches it.
Row-Level-Security bypass is the most common data leak in vibe-coded apps. Here is exactly how it happens, how attackers find it, and how to fix it in Next.js + Supabase with one policy update.
Every week founders tweet about their OpenAI bill going from $10 to $10,000 overnight. Usually the cause is an API key committed to a public repo. Here is why it happens in Next.js specifically and how to stop it in five minutes.