Skip to the solution
HardSystem Design5 min read

How do you handle authentication in Next.js?

769 views

The problem

Question presented to candidate: "How would you implement authentication and authorization in a Next.js App Router app, and where exactly should the checks live?"

What a strong answer should cover:

  • Use a session: a signed, encrypted cookie (stateless) or a session id backed by a database; auth libraries (Auth.js, Clerk, Better Auth and others) handle the details.
  • Proxy (formerly middleware) is good for optimistic redirects based on the cookie, not for full authorization.
  • Verify the session close to the data: a data access layer (DAL) function, wrapped in React cache, called by pages, Server Actions and Route Handlers.
  • Do not rely on layouts: they do not re-render on client navigation, so a layout check can be skipped; pages and layouts also render in parallel.
  • Every Server Action and Route Handler must check the session itself, because they are public endpoints.

Clarifying questions expected:

  • "Stateless cookie or database sessions?" β€” decides revocation and scaling trade-offs.
  • "Is there role-based access?" β€” authorization checks belong in the data layer.

Code / implementation expected: Yes β€” a DAL with a cached session check, and its use in a page and an action.

  • nextjs
  • authentication
  • middleware
  • auth

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

5 min read Β· 8 sections

Read the code

A cached session check in a data access layer
// lib/dal.ts
import "server-only";
import { cache } from "react";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { decrypt } from "@/lib/session";

export const verifySession = cache(async () => {
  const token = (await cookies()).get("session")?.value;
  const session = token ? await decrypt(token) : null;
  if (!session?.userId) redirect("/login");
  return { userId: session.userId, role: session.role };
});

export async function getInvoice(id: string) {
  const { userId, role } = await verifySession();          // checked next to the data
  const invoice = await db.invoice.findUnique({ where: { id } });
  if (!invoice || (invoice.ownerId !== userId && role !== "admin")) return null;
  return { id: invoice.id, total: invoice.total };           // only the fields the UI needs
}

// app/invoices/[id]/page.tsx
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const invoice = await getInvoice((await params).id);
  if (!invoice) return <p>Not found</p>;
  return <p>Total: {invoice.total}</p>;
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 110 of 129 in Next.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Next.js questions
Up nextHard

What are the security considerations for Server Actions in Next.js?

Continue