Quick Answer
Unprotected API routes accept requests from anyone without checking who is calling them. AI code generators like Bolt, Lovable, and Cursor routinely create routes that perform sensitive operations - reading user data, deleting records, sending emails - without any authentication middleware. Adding a single auth check at the middleware layer closes the gap.
Why AI-Generated APIs Skip Authentication
When you prompt an AI tool to "create an API endpoint that returns user orders," it builds the fastest working solution: a route that responds to any HTTP request. Authentication is context that AI models frequently drop because it was not explicit in the prompt and because training examples on the open web are full of tutorial code that skips auth for brevity.
The result is routes like /api/admin/users, /api/delete-account, or /api/send-email sitting wide open in production. According to the OWASP API Security Top 10 (2023), Broken Object Level Authorization and Broken Authentication together account for the top two API security failures. AI-generated codebases amplify this problem because many routes are wired up quickly and never audited.
A 2023 study by Apiiro found that AI-generated code introduces vulnerabilities at 2.74x the rate of human-written code, with authentication bypasses among the most frequently cited categories. GitHub's 2024 Octoverse report noted that over 40% of AI code suggestions in security-sensitive files contained at least one flaw reviewers had to manually correct.
What an Unprotected Route Looks Like
Here is a typical pattern that Cursor or Bolt generates for a Next.js API route. The business logic is correct, but there is no check that the caller is authenticated or authorized.
// ❌ BAD - No authentication check
// pages/api/admin/users.ts (Next.js Pages Router)
import { db } from '@/lib/db';
export default async function handler(req, res) {
if (req.method === 'GET') {
const users = await db.user.findMany();
return res.status(200).json(users);
}
if (req.method === 'DELETE') {
const { userId } = req.body;
await db.user.delete({ where: { id: userId } });
return res.status(200).json({ success: true });
}
}
This route leaks every user record to any unauthenticated caller and lets anyone delete accounts. Because it lives under /api/admin/, a developer might assume it is protected by some server-level rule - but Next.js and Vercel do not add authentication automatically. The path naming means nothing to the runtime.
The Right Pattern: Middleware-Level Auth
The most reliable fix is enforcing authentication at the middleware layer so no request reaches a protected route without a valid session. This means one change protects every route in a directory.
// ✅ GOOD - Auth enforced in Next.js middleware
// middleware.ts (Next.js App Router)
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
export async function middleware(req) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Optional: role check for admin routes
if (req.nextUrl.pathname.startsWith('/api/admin') && token.role !== 'admin') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
return NextResponse.next();
}
export const config = {
matcher: ['/api/admin/:path*', '/api/user/:path*'],
};
With this middleware in place, every route matching the pattern is protected automatically. You do not need to add an auth check inside each individual handler - which is exactly the kind of repetitive work AI tools forget to do.
Common Patterns That AI Tools Miss
| Route Pattern | Risk If Unprotected | Correct Fix |
|---|---|---|
GET /api/users |
Exposes all user records | Require auth token; scope to calling user |
DELETE /api/posts/:id |
Anyone can delete any post | Auth + ownership check (user owns post) |
POST /api/admin/invite |
Anyone can invite users or send emails | Auth + role === 'admin' check |
GET /api/export/csv |
Full data dump available without login | Session required + rate limiting |
POST /api/webhook |
Fake events can trigger business logic | HMAC signature verification |
Object-Level Authorization: The Next Layer
Even after adding authentication, AI-generated routes often skip the second check: does this authenticated user own the resource they are requesting? OWASP calls this Broken Object Level Authorization (BOLA), and it is the leading API vulnerability in the 2023 Top 10.
An authenticated user should only be able to read or modify their own records. If AI generates GET /api/orders/:id that returns any order by ID without verifying ownership, any logged-in user can enumerate and read every order in your system simply by iterating the ID parameter.
The fix is always to scope database queries to the authenticated user's identity - for example, adding where: { id: orderId, userId: session.user.id } to any Prisma or Drizzle query that fetches a user-owned resource.
How to Find Unprotected Routes in Your Codebase
Manual auditing works for small projects but breaks down once a Bolt or Lovable scaffold generates dozens of routes. A systematic approach:
- List every route file. In Next.js App Router, every
route.tsfile underapp/api/is a live endpoint. In Express, look forapp.get(),app.post(),router.delete(), and similar calls. - Check each handler for an auth call. Look for
getServerSession,getToken,verifyJWT, or Supabase'sauth.getUser(). If neither is present and the route touches the database, it is a candidate for exposure. - Test unauthenticated. Use curl or Postman without any Authorization header and call every API route. Any 200 response from a route that should require login is a confirmed finding.
- Run automated scanning. Tools like VibeDoctor (vibedoctor.io) automatically scan your codebase for unprotected API routes and flag specific file paths and line numbers. Free to sign up.
FAQ
Does Supabase Row Level Security replace route-level auth?
Supabase RLS is a defense-in-depth measure at the database layer - it does not replace authentication at the API layer. If your Next.js or Express routes bypass the Supabase client and use the service role key, RLS policies do not apply. Always check authentication in your route handler or middleware first.
How does Next.js middleware differ from checking auth in each route?
Middleware runs before the route handler and can short-circuit the request before any database call is made. Per-route auth checks run after the route is matched. Middleware is more reliable because it prevents handler execution entirely and cannot be accidentally omitted in new routes that fall under the same path pattern.
What about webhook endpoints - should those require auth?
Webhooks from Stripe, GitHub, or other external services cannot carry a user session. Instead, validate an HMAC signature that only the sending service knows. For Stripe webhooks in Node.js, use stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET). Reject any request where signature verification fails.
Can v0 or Lovable-generated code be trusted to include auth?
Only if you explicitly prompt for it and verify the output. These tools generate auth scaffolding when asked, but they do not audit every subsequent route they create. A common failure mode is that the initial login flow is correct but routes added in later prompts - "now add an admin panel" - arrive without auth checks.
What is the difference between authentication and authorization?
Authentication answers "who is this user?" - typically a JWT or session cookie. Authorization answers "is this user allowed to do this thing?" - a role check or ownership check. AI tools almost universally skip both, but authorization bugs (BOLA/IDOR) are more common in AI-generated code because they require business context the model does not have.