NextJS Authentication Best Practices: Secure Your App
OverMCP Team
TL;DR: To add authentication to a Next.js app the right way, use a battle-tested library like NextAuth.js (Auth.js) for flexible OAuth, JWT, or database sessions, protect API routes and pages with middleware, store tokens in HTTP-only cookies, and always validate user input server-side. These nextjs authentication best practices prevent common vulnerabilities like session hijacking, CSRF, and XSS.
Why Authentication Matters for Next.js Apps
Next.js is a powerful React framework for building full-stack applications. However, its hybrid rendering (SSR, SSG, ISR) and API routes introduce unique security challenges. Adding authentication correctly is critical because a single flaw can expose user data or allow unauthorized access. By following nextjs authentication best practices, you ensure that your app remains secure, scalable, and maintainable.
Choosing the Right Authentication Library
While you can build auth from scratch, it's risky. Use a well-maintained library that follows security standards. The most popular choice for Next.js is NextAuth.js (now Auth.js v5), which supports:
For example, to set up NextAuth with Google OAuth:
// app/api/auth/[...nextauth]/route.js
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
};
export const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };Secure Session Management: JWT vs Database
NextAuth offers two session strategies: jwt (default) and database. For most apps, JWT is faster and simpler, but requires careful handling. Nextjs authentication best practices dictate:
NEXTAUTH_SECRET (at least 32 random characters).Example configuration:
// app/api/auth/[...nextauth]/route.js (continued)
export const authOptions = {
// ...providers
session: {
strategy: "jwt",
maxAge: 15 * 60, // 15 minutes
},
jwt: {
maxAge: 60 * 60 * 24 * 30, // 30 days
},
};For database sessions, use an adapter like Prisma:
npm install @prisma/client @auth/prisma-adapterimport { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
export const authOptions = {
adapter: PrismaAdapter(prisma),
// ...
};Protecting API Routes with Middleware
Next.js 12+ supports middleware that runs before every request. Use it to check authentication and redirect unauthenticated users or return 401.
// middleware.js
export { default } from "next-auth/middleware";
export const config = {
matcher: ["/dashboard/:path*", "/api/protected/:path*"],
};For more granular control, use getToken from NextAuth:
// middleware.js
import { withAuth } from "next-auth/middleware";
export default withAuth({
callbacks: {
authorized({ req, token }) {
// Allow if token exists
return !!token;
},
},
});
export const config = { matcher: ["/dashboard/:path*", "/api/protected/:path*"] };For API routes, use getServerSession:
// app/api/protected/route.js
import { getServerSession } from "next-auth";
import { authOptions } from "../auth/[...nextauth]/route";
export async function GET(req) {
const session = await getServerSession(authOptions);
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
return Response.json({ data: "secret" });
}Protecting Pages with getServerSession
For server components, use getServerSession to conditionally render content or redirect:
// app/dashboard/page.jsx
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
export default async function Dashboard() {
const session = await getServerSession(authOptions);
if (!session) {
redirect("/api/auth/signin");
}
return <div>Welcome, {session.user.name}</div>;
}For client components, use the useSession hook:
"use client";
import { useSession } from "next-auth/react";
export default function Profile() {
const { data: session } = useSession();
if (!session) return <p>Access Denied</p>;
return <p>Signed in as {session.user.email}</p>;
}Input Validation and CSRF Protection
Always validate and sanitize user input on the server, even if you do it client-side. NextAuth automatically includes CSRF tokens in its sign-in forms. For custom forms, use the csrfToken from getCsrfToken.
Additional NextJS Authentication Best Practices
Common Mistakes to Avoid
FAQ
What is the best way to handle sessions in Next.js?
The best way is to use NextAuth.js with JWT strategy and HTTP-only cookies. This provides a secure, stateless session that is easy to scale and protects against XSS.
How do I protect API routes in Next.js?
Use getServerSession from NextAuth in each API route to check for a valid session. Alternatively, use middleware with withAuth to protect multiple routes at once.
Should I use JWT or database sessions in NextAuth?
For most apps, JWT is preferred because it's faster and doesn't require a database lookup. Use database sessions if you need to revoke sessions server-side or store additional session data.