← All posts
authorizationvibe-coded appssecurityNext.jsSupabase

Fix Missing Authorization Checks in Vibe-Coded Apps

O

OverMCP Team

Quick answer

Missing authorization checks are one of the most common security holes in vibe-coded apps. AI tools like Cursor, Bolt.new, and v0 often generate code that checks if a user is authenticated but forgets to verify they have permission to access specific resources. Fix this by adding explicit role or ownership checks in every API route and server action that handles user data.

Why vibe-coded apps miss authorization

When you prompt an AI to "build a todo app with Next.js and Supabase," it typically generates authentication flows (login, signup, session management) flawlessly. But authorization—the logic that ensures User A can't delete User B's todos—is often completely absent. The AI focuses on making the app functional and aesthetically pleasing, not on enforcing access control boundaries.

I've seen this pattern across dozens of apps built with Cursor, Lovable, and Replit Agent. The AI assumes a single-user demo scenario and never adds checks like "is this todo owned by the current user?" When you deploy to production, any authenticated user can manipulate any record.

What to check first

Before you launch your app, run through this checklist:

  • Every API route that reads or writes user-specific data: Does it verify the authenticated user owns the resource?
  • Server actions in Next.js App Router: Are you checking userId against the record's owner_id?
  • Dynamic routes like /todos/[id]: Is the ID parameter validated against the user's allowed resources?
  • Supabase Row Level Security (RLS) : Are policies enabled and correct for each table?
  • Firestore rules (if using Firebase): Are you using request.auth.uid to restrict reads/writes?
  • Admin endpoints: Do they check for an admin role, not just a logged-in user?
  • Webhook endpoints: Are you verifying the caller's identity via signature, not just a shared secret?
  • Step-by-step fix

    Let's fix a typical Next.js App Router app built with Cursor that uses Supabase. Suppose you have a route to delete a todo:

    // ❌ Broken: no authorization check
    export async function DELETE(request: Request, { params }: { params: { id: string } }) {
      const supabase = createClient();
      const { error } = await supabase.from('todos').delete().eq('id', params.id);
      if (error) return Response.json({ error }, { status: 500 });
      return Response.json({ success: true });
    }

    This deletes any todo by ID, regardless of ownership. Here's the fix:

    // ✅ Fixed: verify ownership before delete
    export async function DELETE(request: Request, { params }: { params: { id: string } }) {
      const supabase = createClient();
      
      // Get authenticated user
      const { data: { user } } = await supabase.auth.getUser();
      if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
      
      // Fetch the todo and check ownership
      const { data: todo, error: fetchError } = await supabase
        .from('todos')
        .select('user_id')
        .eq('id', params.id)
        .single();
      
      if (fetchError || !todo) return Response.json({ error: 'Not found' }, { status: 404 });
      if (todo.user_id !== user.id) return Response.json({ error: 'Forbidden' }, { status: 403 });
      
      // Now safe to delete
      const { error } = await supabase.from('todos').delete().eq('id', params.id);
      if (error) return Response.json({ error }, { status: 500 });
      return Response.json({ success: true });
    }

    For Supabase RLS, enable it and write policies:

    -- Enable RLS
    ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
    
    -- Policy: users can only delete their own todos
    CREATE POLICY "delete_own_todos" ON todos FOR DELETE
      USING (auth.uid() = user_id);

    Then in your client code, bypass any client-side filters that might be bypassed:

    // Client-side: let RLS handle authorization server-side
    const { error } = await supabase.from('todos').delete().eq('id', todoId);

    Common mistakes

  • Relying solely on client-side checks: AI often generates code that hides UI elements (e.g., delete button) based on user role, but never enforces it server-side. An attacker can send a direct API call.
  • Using Supabase anon key with RLS disabled: The anon key is public. Without RLS, anyone can read/write your entire database. Scan your app for exposed secrets to catch this.
  • Checking authentication but not authorization: Many AI-generated routes verify the user is logged in but never check if they own the resource. This is the #1 vulnerability I find in vibe-coded apps.
  • Hardcoding user IDs in tests or seed data: If you leave a test user ID in production code, it can be exploited.
  • Ignoring Supabase RLS entirely: AI may set up tables without enabling RLS. Always verify with a security scanner for your Vercel deployment.
  • How to automate authorization checks

    Manually auditing every route is tedious. Use a free AI app security scanner to automatically detect missing authorization checks across your codebase. It hooks into your GitHub repo or CI/CD pipeline and flags routes that lack ownership validation, role checks, or proper RLS policies.

    For continuous protection, set up continuous security monitoring to catch new authorization gaps as you iterate with AI.

    FAQ

    What is the difference between authentication and authorization?

    Authentication verifies who you are (login), while authorization determines what you're allowed to do (access control). Vibe-coded apps often implement authentication well but forget authorization entirely.

    Can Supabase RLS alone fix missing authorization?

    RLS is a powerful tool, but it only works if you enable it and write correct policies. It also doesn't cover business logic that RLS can't express (e.g., rate limits, multi-step workflows). Always combine RLS with server-side checks in API routes.

    How can I find missing authorization checks in my app?

    Manually review every route that accesses user-specific data. Look for patterns where you fetch a record by ID without verifying ownership. Automated tools like OverMCP can scan your codebase and highlight routes missing authorization logic.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free