7. Protect Pages and API Routes
Use server-side session checks and enforce authorization for every protected resource.
Protect a Server-Rendered Page
Use getServerSession(req, res, authOptions) in getServerSideProps. Redirect to sign-in if no session exists. Do not rely only on a client-side redirect because it is not a security boundary.
File: `pages/dashboard.js`
import { getServerSession } from "next-auth/next";
import { authOptions } from "./api/auth/[...nextauth]";
export default function Dashboard({ email }) {
return <main><h1>Dashboard</h1><p>Signed in as {email}</p></main>;
}
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions);
if (!session) return { redirect: { destination: "/auth/signin", permanent: false } };
return { props: { email: session.user?.email ?? "" } };
}💡 Do not put secrets or sensitive records in returned props because they are serialized to the browser.
Protect an API Route
Every protected API route must check session and authorization before returning data or performing mutations. Return 401 for unauthenticated requests and 403 for authenticated users without permission.
File: `pages/api/private-data.js`
import { getServerSession } from "next-auth/next";
import { authOptions } from "./auth/[...nextauth]";
export default async function handler(req, res) {
const session = await getServerSession(req, res, authOptions);
if (!session) return res.status(401).json({ error: "Unauthorized" });
if (req.method !== "GET") {
res.setHeader("Allow", "GET");
return res.status(405).json({ error: "Method not allowed" });
}
return res.status(200).json({ message: "Protected data", userId: session.user.id });
}💡 Adjust the relative import path for the actual route location, and validate inputs and permissions for every operation.
Client-Side Guard for UX
Use useSession and a router redirect to improve navigation, but protect the API and data source independently. A client guard is not sufficient to secure a page or backend resource.
Client guard
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useSession } from "next-auth/react";
export default function PrivatePanel() {
const router = useRouter();
const { data: session, status } = useSession();
useEffect(() => {
if (status === "unauthenticated") router.replace("/auth/signin");
}, [status, router]);
if (status !== "authenticated") return <p>Loading…</p>;
return <p>Welcome {session.user?.name ?? session.user?.email}</p>;
}💡 Use this for UI flow only; secure the server endpoints separately.