← All posts
broken access controlvibe codingAI-generated code securityIDORNext.js API security

How to Fix Broken Access Control in Vibe-Coded Apps

O

OverMCP Team

Quick answer

Broken access control vulnerabilities in vibe-coded apps occur when AI-generated code fails to enforce proper authorization checks, allowing users to access, modify, or delete resources they shouldn't. To fix them, audit every API endpoint and server action for missing role or ownership checks, then add explicit authorization middleware or helper functions before any data operation.

What is broken access control in vibe-coded apps?

When you build an app with AI tools like Cursor, Bolt.new, or Lovable, the AI often generates functional endpoints quickly but skips the security logic that ensures only the right users can perform certain actions. Broken access control (BAC) happens when your app doesn't properly verify that a user is allowed to access a resource or perform an operation. The OWASP Top 10 ranks broken access control as the number one security risk, and it's especially common in vibe-coded apps because AI models tend to focus on making things work, not making them secure.

A typical example: your AI-generated Next.js app has an API route /api/users/[id] that returns user data. The AI correctly fetches the user by ID from the database, but it never checks if the authenticated user is an admin or the owner of that profile. Any logged-in user can simply change the [id] parameter and view anyone's data. This is an Insecure Direct Object Reference (IDOR), a type of broken access control.

What to check first

Before diving into code changes, run through this checklist to identify where your app might be vulnerable:

  • [ ] Server actions and API routes: Do any of them omit a user ownership or role check before returning data or performing mutations?
  • [ ] Client-side routing: Are you hiding admin buttons or pages only by not rendering them in the UI? (Attackers can still call the API directly)
  • [ ] Database queries: Are you using the user ID from the request without verifying it belongs to the authenticated user?
  • [ ] File uploads: Can users access files belonging to other users by guessing or enumerating file names?
  • [ ] WebSocket connections: Are you emitting events or data without checking the sender's permissions?
  • [ ] Environment variables and secrets: Does your app expose any admin endpoints that aren't protected by an API key or authentication?
  • You can use a free AI app security scanner to detect common access control issues in your vibe-coded app automatically.

    Step-by-step fix

    Let's walk through fixing a broken access control vulnerability in a typical Next.js App Router API route. Assume you have a route that returns a user's dashboard data:

    // ❌ Vulnerable version — no access control
    // app/api/dashboard/route.ts
    import { NextResponse } from 'next/server';
    import { getServerSession } from 'next-auth';
    import prisma from '@/lib/prisma';
    
    export async function GET(request: Request) {
      const { searchParams } = new URL(request.url);
      const userId = searchParams.get('userId');
    
      if (!userId) {
        return NextResponse.json({ error: 'Missing userId' }, { status: 400 });
      }
    
      const dashboardData = await prisma.dashboard.findUnique({
        where: { userId },
      });
    
      return NextResponse.json(dashboardData);
    }

    This route lets any authenticated user pass any userId and get that user's data. Here's how to fix it:

    // ✅ Fixed version — enforce ownership
    // app/api/dashboard/route.ts
    import { NextResponse } from 'next/server';
    import { getServerSession } from 'next-auth';
    import prisma from '@/lib/prisma';
    
    export async function GET(request: Request) {
      const session = await getServerSession();
      
      if (!session?.user?.id) {
        return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
      }
    
      // Use the authenticated user's ID, not a user-supplied parameter
      const userId = session.user.id;
    
      const dashboardData = await prisma.dashboard.findUnique({
        where: { userId },
      });
    
      if (!dashboardData) {
        return NextResponse.json({ error: 'Not found' }, { status: 404 });
      }
    
      return NextResponse.json(dashboardData);
    }

    For admin-only endpoints, create a reusable middleware or helper:

    // lib/auth-helpers.ts
    export async function requireAdmin() {
      const session = await getServerSession();
      if (!session?.user?.role || session.user.role !== 'admin') {
        throw new Error('Forbidden');
      }
      return session;
    }

    Then use it in your admin routes:

    // app/api/admin/users/route.ts
    export async function GET() {
      try {
        await requireAdmin();
        const users = await prisma.user.findMany();
        return NextResponse.json(users);
      } catch (error) {
        return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
      }
    }

    Additional steps

  • Use a centralized authorization function — Don't scatter role checks across your codebase. Create a single authorize function that you can import anywhere.
  • Never trust user-supplied IDs — Always derive the resource owner from the session or a secure token, not from URL parameters or request bodies.
  • Test with a different user — After fixing, log in as another user and try to access the same endpoint with different IDs. Use a security headers checker to ensure your responses include proper cache-control headers that don't leak data.
  • Common mistakes

    1. Relying on client-side hiding

    AI tools often generate admin panels that hide buttons based on user role in the frontend, but the API routes remain unprotected. An attacker can still call the API directly with a simple curl command.

    2. Using user IDs from URL params in database queries

    This is the most common IDOR pattern. The AI sees req.query.id and uses it directly in a database query without checking if the authenticated user owns that resource.

    3. Forgetting to check ownership on update and delete endpoints

    Developers often protect read endpoints but forget to add checks on PUT, PATCH, or DELETE routes. An attacker could delete another user's data by sending a DELETE request with their ID.

    4. Not verifying object-level permissions in list endpoints

    A list endpoint like /api/posts might return all posts in the database, including private ones. Always filter results based on the user's permissions.

    5. Hardcoding admin IDs or roles in environment variables

    Some vibe-coded apps store a list of admin user IDs in a .env file. This is brittle and insecure. Use proper role-based access control (RBAC) with a database or auth provider.

    How to prevent broken access control in future vibe-coded sessions

    When you prompt an AI coding tool, include security requirements in your initial request. For example:

    "Create an API endpoint that returns the current user's profile. The endpoint must only return data for the authenticated user — never accept a user ID from the client. Use the session to identify the user."

    After generating code, always review the authorization logic. Run a secret leak scanner to ensure no keys or passwords are exposed in your codebase, and consider continuous security monitoring for ongoing protection.

    For apps deployed on platforms like Vercel, you can use a Vercel security scanner to automatically detect access control issues before they hit production.

    FAQ

    What is the difference between authentication and access control?

    Authentication verifies who you are (e.g., logging in with email and password). Access control determines what you're allowed to do (e.g., viewing only your own orders). Broken access control happens when authentication is in place but authorization checks are missing.

    Can a free tool detect broken access control?

    Yes, automated scanners can detect some patterns like missing authorization checks in API routes, but they may not catch all logic-level flaws. Use them as a first pass, then manually review critical endpoints. OverMCP's free AI app security scanner can help identify common BAC patterns.

    How do I test for broken access control manually?

    Log in as User A and try to access User B's resources by changing IDs in URLs, API requests, or hidden form fields. For example, change /api/orders/123 to /api/orders/456. If you see another user's data, you've found a vulnerability.

    ---

    Broken access control is the most common security flaw in vibe-coded apps, but it's also one of the easiest to fix once you know what to look for. By adding explicit authorization checks to every server endpoint and using the session to identify users instead of trusting client-supplied data, you can close the most dangerous gaps in your AI-built application.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free