← All posts
Next.jsauthenticationsecurityNextAuthJWT

NextJS Authentication Best Practices: Secure Your App

O

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:

  • OAuth providers (Google, GitHub, etc.)
  • Email/password with database adapters
  • JWT or database sessions
  • Built-in CSRF protection
  • 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:

  • Store JWTs in HTTP-only cookies to prevent XSS access.
  • Set a short expiration (e.g., 15 minutes) with a longer refresh token.
  • Use a strong 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-adapter
    import { 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

  • Use environment variables for secrets, never hardcode.
  • Enable HTTPS in production to prevent token interception.
  • Implement rate limiting on auth endpoints to prevent brute force.
  • Log all authentication events for auditing.
  • Regularly update dependencies to patch vulnerabilities.
  • Scan your code for exposed secrets before deployment. Tools like OverMCP can automatically detect leaked API keys and secrets in your Next.js repositories, helping you catch issues early.
  • Common Mistakes to Avoid

  • Storing tokens in localStorage: Vulnerable to XSS. Use HTTP-only cookies.
  • Rolling your own crypto: Use established libraries.
  • Ignoring CORS: Properly configure allowed origins.
  • Not validating redirect URLs: Prevent open redirect attacks.
  • 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.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free