How to Avoid: Protected Routes Missing Auth Checks
July 13, 2026 · 8-minute read · Fairy
The short answer
To prevent protected routes missing auth checks in AI-generated code, implement authentication middleware at the router or framework level rather than per-route, use explicit allow-lists for public paths, and audit every route against your intended access policy. AI often generates routes that look protected but lack actual session or token verification.
The Direct Answer: How to Prevent Missing Auth Checks
Protected routes missing authentication checks is one of the most common and severe security failures in AI-generated code. To prevent it, implement authentication middleware at the router or framework level, use an explicit allow-list for public paths rather than a deny-list for protected ones, and systematically verify that every route requiring authentication actually enforces it.
AI code generators frequently produce routes that appear protected but lack actual session or token verification. The code compiles, the route responds, the tests pass—but anyone can access it without logging in.
Why AI Generates Unprotected Routes
Understanding why this happens helps you catch it. AI models generate code based on patterns in their training data, optimizing for functionality over security completeness.
Pattern Copying Without Context
When AI generates a new route handler, it often copies the structure of existing routes without understanding the security context. If you ask for a /api/admin/users endpoint, you'll get a working handler that queries users from the database. What you might not get is the middleware that should protect it.
The AI sees routes as input-processing-output pipelines. It doesn't inherently understand that some pipelines need a gate at the entrance.
Implicit Assumptions About Architecture
AI frequently assumes authentication exists "somewhere else" in the application. It generates route handlers that reference req.user or session.userId without ensuring those values are actually populated by middleware earlier in the request chain.
This creates code that works perfectly in isolation but fails open in production.
Path Matcher Gaps
Even when AI includes authentication middleware, it often configures path matchers incorrectly. A route protected by middleware matching /api/admin/* won't protect /api/admin (no trailing path). A matcher for /dashboard/:page won't protect /dashboard.
These subtle gaps in route patterns are easy for AI to produce and hard for humans to spot in review.
Real Patterns We've Observed
Fairy's code reviews have surfaced several concrete patterns of missing auth checks that appear repeatedly in AI-generated applications.
Admin Routes Bypassable via Query Parameters
One particularly dangerous pattern: admin authentication that checks a query parameter or request body instead of verifying a real session.
Vulnerable pattern:
// AI-generated admin check - INSECURE
app.get('/api/admin/users', (req, res) => {
if (req.query.admin === 'true') {
return User.findAll().then(users => res.json(users));
}
return res.status(403).json({ error: 'Forbidden' });
});
Anyone can add ?admin=true to the URL and access admin endpoints. The AI generated code that "checks" for admin access, but the check is meaningless because it trusts client-supplied claims.
Secure pattern:
// Proper admin check
const requireAdmin = async (req, res, next) => {
const session = await getSession(req);
if (!session?.userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
const user = await User.findById(session.userId);
if (user?.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
req.user = user;
next();
};
app.get('/api/admin/users', requireAdmin, (req, res) => {
return User.findAll().then(users => res.json(users));
});
Routes Added After Initial Auth Setup
A common scenario: you set up authentication correctly when building the initial routes. Later, you ask AI to add new functionality. The new routes don't include the auth middleware because AI doesn't see the full application context.
// Initial setup - protected
app.use('/api/account', authMiddleware);
app.get('/api/account/profile', getProfile);
app.put('/api/account/settings', updateSettings);
// Later addition by AI - NOT protected
app.get('/api/user/data', getUserData); // Missing authMiddleware
app.post('/api/user/export', exportUserData); // Missing authMiddleware
The new /api/user/* routes look like they belong with the protected routes, but they're not covered by the /api/account middleware scope.
Middleware Order Issues
Authentication middleware must run before route handlers. AI sometimes generates code where the order is wrong:
// Wrong order - routes defined before middleware
app.get('/api/protected', (req, res) => {
// This runs without auth
res.json({ data: req.user }); // req.user is undefined
});
// Middleware defined after routes won't protect them
app.use(authMiddleware);
How to Detect Missing Auth Checks
Detection requires systematic verification, not just visual inspection.
Audit Routes Against Middleware Chain
For every route in your application, trace the middleware chain that executes before it. Express, Fastify, and similar frameworks execute middleware in definition order. A route is only protected if auth middleware appears earlier in the chain and covers that route's path.
Create a checklist:
- List all routes that should require authentication
- For each route, identify which middleware runs before it
- Verify at least one middleware checks session/token validity
- Test each route without authentication to confirm it rejects the request
Check Path Matcher Coverage
Path matchers have subtle semantics that vary by framework:
/api/*matches/api/usersbut might not match/apiitself/api/:resourcematches/api/usersbut not/api/users/123- Trailing slashes matter in some frameworks
Test your actual routes against your actual matchers. Don't assume coverage.
Look for Session References Without Validation
Search your codebase for references to req.user, session.userId, ctx.user, or similar patterns. Each reference should be preceded by middleware that validates and populates these values. If you find a handler that reads req.user.id but no middleware verifies req.user exists, you have a missing auth check.
Watch for Conditional Returns
AI-generated auth checks often return early on authentication failure but continue execution otherwise:
// Subtle bug: auth check that doesn't stop execution
app.get('/api/data', async (req, res) => {
if (!req.session?.userId) {
res.status(401).json({ error: 'Unauthorized' });
// Missing return - execution continues!
}
const data = await getData(req.session.userId);
res.json(data);
});
The response is sent twice, and getData(undefined) executes. Always verify auth check failures terminate the request.
The Correct Pattern: Secure by Default
The most reliable approach inverts the default: protect everything, then explicitly allow public access.
Global Auth Middleware with Public Allow-List
const PUBLIC_PATHS = [
'/',
'/health',
'/api/auth/login',
'/api/auth/register',
'/api/auth/forgot-password',
];
const authMiddleware = async (req, res, next) => {
// Allow public paths
if (PUBLIC_PATHS.includes(req.path)) {
return next();
}
// Everything else requires authentication
const session = await getSession(req);
if (!session?.userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
req.user = await User.findById(session.userId);
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
};
// Apply globally, before any routes
app.use(authMiddleware);
With this pattern, new routes are protected by default. You must explicitly add paths to PUBLIC_PATHS to make them accessible without authentication. This is the opposite of the typical AI-generated pattern where protection is opt-in.
Type-Safe Route Definitions
In TypeScript applications, you can enforce auth requirements at the type level:
type AuthenticatedRequest = Request & {
user: User; // Guaranteed to exist
};
type PublicRequest = Request; // No user guaranteed
// Route handler types enforce the contract
type AuthenticatedHandler = (req: AuthenticatedRequest, res: Response) => void;
type PublicHandler = (req: PublicRequest, res: Response) => void;
// The compiler catches missing auth
const getProfile: AuthenticatedHandler = (req, res) => {
// req.user is guaranteed by the type
res.json(req.user);
};
Route Registration Patterns
Use explicit route registration that makes auth requirements visible:
// Registration helpers that enforce intent
const publicRoute = (method, path, handler) => {
app[method](path, handler);
console.log(`[PUBLIC] ${method.toUpperCase()} ${path}`);
};
const protectedRoute = (method, path, handler) => {
app[method](path, authMiddleware, handler);
console.log(`[PROTECTED] ${method.toUpperCase()} ${path}`);
};
const adminRoute = (method, path, handler) => {
app[method](path, authMiddleware, requireAdmin, handler);
console.log(`[ADMIN] ${method.toUpperCase()} ${path}`);
};
// Usage makes intent explicit
publicRoute('get', '/api/health', healthCheck);
protectedRoute('get', '/api/profile', getProfile);
adminRoute('get', '/api/admin/users', listUsers);
Testing for Missing Auth Checks
Automated tests should verify authentication requirements.
Negative Authentication Tests
For every protected route, write a test that calls it without authentication and expects a 401:
describe('Protected routes', () => {
const protectedRoutes = [
{ method: 'GET', path: '/api/profile' },
{ method: 'PUT', path: '/api/settings' },
{ method: 'GET', path: '/api/admin/users' },
];
protectedRoutes.forEach(({ method, path }) => {
it(`${method} ${path} requires authentication`, async () => {
const res = await request(app)[method.toLowerCase()](path);
expect(res.status).toBe(401);
});
});
});
Route Inventory Tests
Maintain a test that verifies your route inventory matches reality:
it('all routes are accounted for', () => {
const registeredRoutes = getRegisteredRoutes(app);
const documentedRoutes = [...PUBLIC_ROUTES, ...PROTECTED_ROUTES, ...ADMIN_ROUTES];
const undocumented = registeredRoutes.filter(
r => !documentedRoutes.some(d => d.path === r.path && d.method === r.method)
);
expect(undocumented).toEqual([]);
});
This catches routes added by AI that weren't explicitly categorized.
Integration with AI Code Review
When using AI to generate code, specific practices reduce auth check failures.
Explicit Context in Prompts
Tell AI about your authentication architecture:
"Our application uses session-based authentication. All routes under /api except /api/auth/* require the authMiddleware. When generating new routes, include authMiddleware in the route definition."
Review Checklist for AI Output
Before accepting AI-generated routes:
- Does the route include auth middleware?
- Is the path covered by existing global middleware?
- Does the handler reference user/session data?
- If yes to #3, is that data validated before use?
Automated Verification
Tools like Fairy Scout can automatically detect routes missing authentication checks during code review. Automated verification catches the patterns AI produces repeatedly—query parameter auth bypasses, missing middleware, path matcher gaps—before they reach production.
For ongoing monitoring of authentication patterns across your codebase, Fairy for Code provides continuous oversight that persists context across sessions and catches regressions as your application evolves.
Summary
Protected routes missing auth checks is a critical security failure that AI code generators produce frequently. The fix is architectural:
- Default to protected: Use global auth middleware with explicit public path allow-lists
- Verify systematically: Audit every route against your middleware chain
- Test negative cases: Every protected route needs a test confirming it rejects unauthenticated requests
- Make intent visible: Use registration patterns that clearly indicate auth requirements
AI does the work of generating routes. Your job—or your verification system's job—is ensuring every route that should be protected actually is.
Frequently asked questions
Why does AI-generated code sometimes skip authentication on protected routes?
AI models optimize for making code work, not for security completeness. They often copy route patterns without copying the auth middleware, or assume authentication exists elsewhere in the application without verifying it.
How can I detect routes missing authentication checks?
Audit your route definitions against your middleware chain. Look for routes that don't pass through auth middleware, path-matcher gaps that exclude certain URL patterns, and endpoints that access user data without session verification.
Should I use per-route or global authentication middleware?
Use global authentication middleware with an explicit allow-list for public routes. This inverts the security model so new routes are protected by default, reducing the risk of accidentally exposing sensitive endpoints.
What's the difference between authentication and authorization checks?
Authentication verifies the user's identity (who they are), while authorization verifies their permissions (what they can access). Both can be missing in AI-generated code, but missing auth checks are more common and more severe.
Have AI-generated work you’d want verified? Connect with a Fairy → or run a free check with Scout.
More resources