Protecting Pages and Actions

Pages

Call auth() in a Server Component. It reads the session cookie for this request.

// app/account/page.tsx
import { redirect } from 'next/navigation';
import { auth } from '@/auth';

export default async function AccountPage() {
  const session = await auth();
  if (!session?.user) redirect('/login?next=/account');

  return <h1>Hi {session.user.name}</h1>;
}

Route Handlers and Server Actions

// app/api/orders/route.ts
import { auth } from '@/auth';
import { prisma } from '@/lib/prisma';

export async function GET() {
  const session = await auth();
  if (!session?.user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
  const orders = await prisma.order.findMany({ where: { userId: session.user.id } });
  return Response.json(orders);
}
// app/account/actions.ts
'use server';

import { auth } from '@/auth';

export async function updatePhone(phone: string) {
  const session = await auth();
  if (!session?.user) throw new Error('Unauthorized'); // actions are public endpoints!
  await prisma.user.update({ where: { id: session.user.id }, data: { phone } });
}

Note: Checking auth only in middleware or only in the layout is not enough. Layouts do not re-run on every navigation, and Server Actions can be called directly. Check close to the data.

Middleware as a First Gate

// middleware.ts (proxy.ts in Next.js 16)
export { auth as middleware } from '@/auth';

export const config = { matcher: ['/account/:path*', '/admin/:path*', '/checkout/:path*'] };
// auth.ts — decide in the "authorized" callback
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [/* ... */],
  callbacks: {
    authorized({ auth, request }) {
      const isAdminPath = request.nextUrl.pathname.startsWith('/admin');
      if (isAdminPath) return auth?.user?.role === 'admin';
      return !!auth?.user; // false -> redirect to the sign-in page
    },
  },
});