An admin route that checks if (!session) but not if (session.user.role !== 'admin') is accessible to every authenticated user in the application. This exposes user management, system configuration, and privileged operations to any account holder. CWE-285 (Improper Authorization) and CWE-284 (Improper Access Control) define this failure precisely. OWASP A01 (Broken Access Control) lists this as a primary failure mode. NIST 800-53 AC-3 and AC-6 require that admin operations enforce both authentication and the specific privilege level required — the authentication check alone is necessary but not sufficient.
Critical because any authenticated user gains access to administrative functionality — user management, billing, system configuration — without needing any elevated credentials.
Use middleware.ts to enforce admin role checks across the entire /admin path group before requests reach individual handlers. Keep role checks in handlers as well for defense in depth.
// middleware.ts
export async function middleware(req: NextRequest) {
const token = await getToken({ req });
if (req.nextUrl.pathname.startsWith('/admin')) {
if (!token) return NextResponse.redirect(new URL('/login', req.url));
if (token.role !== 'admin') return NextResponse.redirect(new URL('/403', req.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/admin/:path*', '/api/admin/:path*'],
};
Middleware can be bypassed in some configurations — individual admin API handlers should also check session.user.role === 'admin' as a second line of defense.
ID: saas-authorization.admin-privilege.admin-routes-protected
Severity: critical
What to look for: Count all relevant instances and enumerate each. Find admin-only routes — URL patterns like /admin/*, /api/admin/*, /dashboard/admin/*, or routes named with "admin" that handle user management, system configuration, or privileged operations. For each, check whether the handler verifies the admin role specifically, not just whether the user is authenticated. An admin route that only checks if (!session) but not if (session.user.role !== 'admin') is a fail.
Pass criteria: All admin routes and admin API endpoints explicitly check for the admin role (or equivalent high-privilege permission) in addition to checking for authentication. At least 1 implementation must be verified. Both checks must be present.
Fail criteria: Any admin-purpose route or endpoint only validates authentication without also verifying the admin role, making all admin functionality accessible to any authenticated user.
Skip (N/A) when: No admin section or admin-specific routes detected in the codebase.
Detail on fail: "Admin route(s) check authentication but not admin role. Any authenticated user can access admin functionality." (List the specific routes that are unprotected.)
Remediation: For Next.js, use middleware.ts to enforce admin role checks across the entire /admin path group before the request reaches route handlers.
// middleware.ts
export async function middleware(req: NextRequest) {
const token = await getToken({ req });
if (req.nextUrl.pathname.startsWith('/admin')) {
if (!token) return NextResponse.redirect(new URL('/login', req.url));
if (token.role !== 'admin') return NextResponse.redirect(new URL('/403', req.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/admin/:path*', '/api/admin/:path*'],
};
Even with middleware protection, keep role checks in individual admin API handlers as defense in depth — middleware can be bypassed in some configurations.