Your AI-built app checks if users are logged in but not what they can access. Here's how broken authorization slips into Next.js apps and how to find it.
Your app has login. Sessions work. Protected routes redirect to /signin when you log out. You ship. Then a user changes a number in the URL from 42 to 43 and sees somebody else's invoice. This is broken object-level authorization, and it is the single most common bug I see in apps built with Cursor, Lovable, and Bolt. The reason is simple. When you prompt an AI to build a dashboard, it asks itself one question: is this user logged in? If yes, return the data. It does not ask the second question, the one that actually matters: does this specific user own this specific record? OWASP ranks this as the number one API risk for a reason. Authentication answers who you are. Authorization answers what you are allowed to touch. AI code generators are great at the first and casual about the second, because the first is visible in the UI and the second only shows up when someone goes looking. Most founders never look.
02Where it hides in your Next.js app
Open app/api in your Next.js project and look at every route handler that returns user data. The pattern you want to find is a Supabase or Prisma query that filters by an ID from the request but never by the current user. Something like this is the smell: const { id } = params; const order = await db.order.findUnique({ where: { id } }); return Response.json(order). That handler will happily return any order to any logged-in user who guesses the ID. The fix is one extra clause: where: { id, userId: session.user.id }. Dashboards are the worst offenders because the AI assumes the URL is trusted. It is not. Anyone can edit /dashboard/orders/42 in the address bar. Server actions have the same problem. If your action accepts an orderId argument and looks it up without scoping to the session user, you have an IDOR. Cursor will write this code in a heartbeat and never mention it. Check every dynamic route segment, every POST body field named id, projectId, workspaceId, or fileId, and every place you pass an ID from the client back to the server.
03The five-minute check you can run today
You do not need a pentest to find this. You need two browser profiles and ten minutes. Sign up for your own app as User A. Create a record. Note the URL or the ID in the network tab. Open an incognito window, sign up as User B, and paste User A's URL. If you see the data, you have a bug. Now do the same thing for every API route. Open devtools, hit your own dashboard, copy the fetch as cURL, then re-run that cURL with User B's session cookie. Same test, different layer. If the response status is 200 instead of 403 or 404, that endpoint is broken. While you are in there, check delete and update endpoints too. A read leak is bad. A cross-tenant delete is a deletion bug that ends a startup. Write a single integration test per resource that signs in as User B and asserts a 403 against User A's resource. Once you have that test, AI cannot regress you. Without it, the next prompt that touches the route will reintroduce the bug and you will never know.
04Make authorization the default, not an afterthought
The structural fix is to stop trusting IDs from clients. Build a helper called getOwnedResource(table, id, userId) and route every read through it. If a query in your codebase does not go through that helper, treat it as a bug. For multi-tenant apps, scope by workspaceId at the middleware layer so route handlers never see records they should not. If you use Supabase, enable Row Level Security on every table and write policies that compare auth.uid() to the row's user_id column. RLS is your seatbelt when the application layer forgets. Add a CI check that greps for findUnique, findFirst, and .from() calls without a userId or workspace filter and fails the build. Five minutes of setup, years of protection. Next time you prompt Cursor for a new endpoint, paste this into the prompt: 'every query must filter by the current session user id, return 404 for resources the user does not own, never trust IDs from the request body.' That one sentence prevents 80 percent of the bugs in this article. The other 20 percent is what scanning is for.
Find broken authorization before your users do
Guardian scans your app for missing authorization checks and shows the exact lines to review.
Scan my app free