AI coding tools love the Next.js parens-prefix convention — they will happily scaffold app/(authenticated)/dashboard/page.tsx or app/(app)/settings/page.tsx and then forget that the parenthesized segment is purely a routing grouping, not a security boundary. The directory name carries no runtime effect; if the page file itself never calls auth(), getServerSession(), or supabase.auth.getUser(), anyone who guesses the URL gets the page rendered, HTML streamed, and server-component data leaked. Facebook's 2018 token-flaw breach exposed 50 million accounts through a similar gap between "this route is supposed to be protected" and "this route actually checks the session server-side." OWASP ranks Broken Access Control as the #1 web risk in its 2021 Top 10 precisely because this class of failure is this easy to ship. The failure is especially cruel because the app feels secure during development — the team tests logged-in, sees the dashboard, ships. The anonymous-traffic path is never exercised until an attacker finds it.
Critical because an unguarded protected-group route streams private user data and admin surfaces to anonymous requests — a complete access-control failure with zero mitigation until the session getter is wired in.
Add a server-side session check at the top of every protected route group's layout file, and have the layout redirect unauthenticated requests before any child route renders:
// app/(authenticated)/layout.tsx
import { redirect } from 'next/navigation';
import { auth } from '@/lib/auth';
export default async function AuthenticatedLayout({ children }: { children: React.ReactNode }) {
const session = await auth();
if (!session?.user) redirect('/login');
return <>{children}</>;
}
A single layout gate covers every descendant page.tsx and nested layout.tsx — you do not need a check in each file if the parent layout redirects. Client-only guards ('use client' + useSession()) are not sufficient because the server still ships the protected HTML before the client-side check runs. For the full authorization pass including role checks, route-level RBAC, and admin-action audit logging, run the saas-authentication and security-hardening Pro audits.
project-snapshot.security.protected-routes-call-session-gettercriticalapp/(authenticated)/, app/(app)/, app/(dashboard)/, app/(protected)/, app/(private)/, app/(user)/. Walk each page.tsx / layout.tsx / route.ts. For each, check whether the file itself OR its closest parent layout.tsx (walking upward) calls a session getter: auth(), getServerSession(), getSession(), supabase.auth.getUser(), requireUser(), requireAuth(), currentUser(), clerkAuth(), getKindeServerSession().'use client' component using useSession() / useUser() does NOT count — the protected HTML has already streamed before the client check runs and is bypassable."(authenticated) covered by app/(authenticated)/layout.tsx: auth() + redirect('/login')"."app/(dashboard)/settings/page.tsx renders with no server session check; app/(dashboard)/layout.tsx missing entirely; parens grouping is routing-only".// app/(authenticated)/layout.tsx
import { redirect } from 'next/navigation';
import { auth } from '@/lib/auth';
export default async function Layout({ children }: { children: React.ReactNode }) {
const session = await auth();
if (!session?.user) redirect('/login');
return <>{children}</>;
}
A single layout gate covers every descendant page.