Mastering Authentication in Next.js 15: The Senior Engineer's Guide

Mastering Authentication in Next.js 15: The Senior Engineer's Guide

L
Learning Blog
30 min read
0 views
Share:

1. The Architecture: "One Config to Rule Them All"

In NextAuth v4, configuration was often trapped inside an API route. In v5, we lift the config into a standalone auth.ts file. This allows us to use the same logic in Middleware, Server Actions, and API routes.

Step 1: Initialize the Auth Engine

Create a file at your root (or /src) called auth.ts. This is the brain of your auth system.

TypeScript

// auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";

export const { 
  handlers, 
  auth, 
  signIn, 
  signOut 
} = NextAuth({
  providers: [GitHub],
  // Senior Tip: Always use the 'jwt' strategy for better scalability 
  // in serverless environments.
  session: { strategy: "jwt" },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
      if (isOnDashboard) {
        if (isLoggedIn) return true;
        return false; // Redirect unauthenticated users to login page
      }
      return true;
    },
  },
});

2. The Route Handler

Next, we need to expose the authentication API. Thanks to our setup above, the route handler is now just two lines of code.

TypeScript

// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;

3. Protecting Routes: Middleware vs. Server Components

As a senior dev, I recommend Middleware for global "gates" and Server Components for granular data protection.

The Middleware Gate

This prevents unauthenticated users from even hitting your server components for protected routes.

TypeScript

// middleware.ts
export { auth as middleware } from "@/auth";

export const config = {
  // Protect everything except public assets and login
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

The Server Component Check

Inside a page, you can access the session without any "useEffect" or "loading" states. It’s instant and secure.

TypeScript

// app/dashboard/page.tsx
import { auth } from "@/auth";

export default async function Dashboard() {
  const session = await auth();

  if (!session) return <div>Please log in</div>;

  return (
    <main>
      <h1>Welcome back, {session.user?.name}</h1>
      {/* Personalized content rendered on the server */}
    </main>
  );
}

4. The Client Side: Interactivity

While we prefer server-side checks, you still need buttons. Use the exported signIn and signOut functions.

TypeScript

// components/AuthButtons.tsx
import { signIn, signOut } from "@/auth";

export function LoginButton() {
  return (
    <form action={async () => {
      "use server";
      await signIn("github");
    }}>
      <button type="submit">Sign in with GitHub</button>
    </form>
  );
}

Senior Engineering Checklist

  1. Secret Management: Ensure AUTH_SECRET is set in your .env. Without it, your production build will fail.
  2. Type Safety: If you're adding custom fields (like role) to the user object, remember to extend the Session interface in a next-auth.d.ts file.
  3. Edge Compatibility: Auth.js v5 is built to run on the Edge. Avoid importing heavy Node-only libraries in your main auth.ts if you plan to use Edge Runtime.

Topics

Share this article

Share:
L

About Learning Blog

Passionate about web development and continuous learning. Sharing insights, tutorials, and lessons learned from my journey in software engineering.

Learn more about me
Related Content

Ad Placeholder

Slot: MULTIPLEX_AD_SLOT_1