What Is NextAuth.js? Authentication for AI-Built Next.js Apps
When you tell AI to "add Google login," it generates NextAuth.js. Here's what every piece does — and what breaks when you don't understand it.
TL;DR
NextAuth.js (now called Auth.js) is the library AI generates when you ask it to add login to a Next.js app. It handles sign-in with Google, GitHub, or email — managing user sessions so your app knows who's logged in. Think of it as a complete key card system for your building: it checks IDs at the door, issues access badges, and remembers who's inside.
Why AI Coders Need to Understand This
Authentication is one of the first "real" features you'll add to any app. The moment your project goes from a personal tool to something other people use, you need login. And when you ask Claude, ChatGPT, or Cursor to add login to a Next.js app, NextAuth.js is what they reach for about 90% of the time.
The problem? AI generates a lot of files for authentication. Suddenly you've got a [...nextauth] route file, a SessionProvider wrapping your whole app, useSession hooks scattered through components, environment variables you've never seen, and callback URLs pointing to places that don't exist yet. It feels like you asked for a doorknob and got handed blueprints for an entire security system.
That's because you basically did. Authentication is genuinely complex — it involves cryptography, token management, third-party API integrations, and session persistence. NextAuth.js handles all of that for you. But you still need to understand what each piece does, because when something breaks (and it will), AI's fix-it suggestions are often wrong.
This article walks you through every piece of NextAuth.js code your AI generates, explains what it does in plain English, and shows you exactly what goes wrong and how to fix it.
The Real Scenario: You Asked AI for Login
"Add Google login to my Next.js app. Users should be able to sign in with their Google account and see their name on the dashboard. Protect the dashboard page so only logged-in users can see it."
Seems simple, right? You just want a "Sign in with Google" button. But your AI knows that this actually requires:
- OAuth integration — talking to Google's servers to verify someone's identity (learn how OAuth works)
- Session management — remembering that this person is logged in as they navigate between pages
- Protected routes — blocking unauthorized users from seeing the dashboard
- Token handling — securely storing proof of identity (what JWT tokens are)
So it generates NextAuth.js — a library specifically designed to handle all of this in Next.js applications. Let's look at exactly what it creates.
What AI Generated: The Complete NextAuth Setup
When your AI adds NextAuth.js, it typically creates or modifies 4–6 files. Here's what each one does.
1. The NextAuth Route Handler (The Security Desk)
This is the brain of the operation. Think of it as the security desk in a building lobby — everyone who wants in or out passes through here.
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
secret: process.env.NEXTAUTH_SECRET,
})
export { handler as GET, handler as POST }
What this does: Creates an API endpoint at /api/auth/* that handles every authentication action — signing in, signing out, and managing sessions. The [...nextauth] part is a Next.js "catch-all" route, meaning it handles /api/auth/signin, /api/auth/callback/google, /api/auth/signout, and more — all from this one file.
The providers array tells NextAuth which sign-in methods to offer. Here it's just Google, but you could add GitHub, Discord, email/password, or any of 80+ supported services.
2. The Session Provider (The Badge System)
This wraps your entire app so any component can check who's logged in. It's like a building-wide badge reader system — once installed, any room can verify if someone belongs there.
// app/providers.tsx
"use client"
import { SessionProvider } from "next-auth/react"
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
// app/layout.tsx
import { Providers } from "./providers"
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
What this does: The SessionProvider makes login state available everywhere in your app. Without it, your components have no idea whether anyone is signed in. The "use client" at the top is required because session state is a browser-side feature — sessions need to live where the user interacts.
3. The useSession Hook (The Badge Reader)
This is how individual pages or components check if someone's logged in and who they are.
// app/dashboard/page.tsx
"use client"
import { useSession } from "next-auth/react"
import { redirect } from "next/navigation"
export default function Dashboard() {
const { data: session, status } = useSession()
if (status === "loading") return <p>Loading...</p>
if (status === "unauthenticated") redirect("/api/auth/signin")
return (
<div>
<h1>Welcome, {session?.user?.name}</h1>
<p>Email: {session?.user?.email}</p>
<img src={session?.user?.image} alt="Profile" />
</div>
)
}
What this does: useSession() returns three things: the session data (user name, email, profile picture), the status ("loading", "authenticated", or "unauthenticated"), and an update function. This component checks the badge — if you don't have one, you get redirected to the sign-in page.
4. Sign-In and Sign-Out Buttons
// components/AuthButton.tsx
"use client"
import { signIn, signOut, useSession } from "next-auth/react"
export function AuthButton() {
const { data: session } = useSession()
if (session) {
return (
<div>
<p>Signed in as {session.user?.name}</p>
<button onClick={() => signOut()}>Sign Out</button>
</div>
)
}
return <button onClick={() => signIn("google")}>Sign in with Google</button>
}
What this does: The signIn("google") function tells NextAuth to start the Google login flow. The signOut() function clears the session. These are the actual door handles your users interact with.
5. Environment Variables (The Master Keys)
# .env.local
GOOGLE_CLIENT_ID=your-google-client-id-here
GOOGLE_CLIENT_SECRET=your-google-client-secret-here
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-random-secret-string-here
What this does: These are the credentials and configuration that NextAuth needs to talk to Google and secure your sessions. They're stored in a .env.local file that never gets committed to Git. If you're not familiar with environment variables, read our guide on environment variables — they're critical for every real app.
Understanding Each Part
Now that you've seen the code, let's break down the four core concepts that make NextAuth tick.
Providers: Who Can Enter the Building
Providers are the sign-in methods your app supports. Each provider is like a different type of accepted ID — Google is a driver's license, GitHub is a work badge, email/password is a PIN code.
The most common providers AI generates:
- GoogleProvider — "Sign in with Google." Requires a Client ID and Secret from the Google Cloud Console.
- GitHubProvider — "Sign in with GitHub." Popular for developer tools. Requires an OAuth App from GitHub Settings.
- CredentialsProvider — Email/password login you manage yourself. AI generates this for custom auth, but it's the most error-prone option because you handle password security.
- EmailProvider — Magic link login (sends a sign-in link to the user's email). Requires an email service like Resend or SendGrid.
You can mix and match. Want Google and GitHub? Just add both to the providers array. NextAuth builds the sign-in page automatically.
Callbacks: Custom Rules at the Security Desk
Callbacks let you customize what happens during the authentication process. They're like special instructions for the security desk: "If this person works in Building B, add a B-wing sticker to their badge."
callbacks: {
async jwt({ token, user }) {
// When user first signs in, add their role to the token
if (user) {
token.role = user.role
}
return token
},
async session({ session, token }) {
// Make the role available in the session
session.user.role = token.role
return session
}
}
The two callbacks you'll see most often:
jwtcallback — Runs every time a token is created or accessed. This is where you add custom data (like user roles or subscription status) to the token.sessioncallback — Runs every timeuseSession()is called. This controls what data your components can actually access. If you add something in the JWT callback but don't pass it through here, your components won't see it.
AI frequently generates callbacks when you ask for role-based access or custom user data. If you see callbacks in your generated code and didn't ask for custom features, the AI might be over-engineering — the defaults work fine for basic login.
Sessions: Remembering Who's Inside
A session is how your app remembers that someone is logged in as they navigate between pages. Without sessions, you'd need to log in again every time you clicked a link — like showing your badge at every single door in the building.
NextAuth supports two session strategies:
- JWT (default) — Stores the session data in an encrypted cookie in the user's browser. No database needed. The cookie travels with every request, like carrying your badge on a lanyard.
- Database — Stores sessions in your database and only puts a session ID in the cookie. More secure for sensitive apps because you can revoke sessions server-side, like a central security office that can deactivate any badge remotely.
For most AI-built apps, the JWT default is fine. You only need database sessions if you need to invalidate sessions on demand (like "log out all devices" functionality).
Adapters: Connecting to a Database
Adapters are the bridge between NextAuth and your database. If you're using JWT sessions with no database, you don't need an adapter at all — and this is one of the biggest sources of confusion in AI-generated auth code.
AI will sometimes generate a Prisma adapter or a Drizzle adapter because it assumes you want to store users in a database. If you just want "Sign in with Google" and don't care about persisting user data, you can safely remove the adapter code.
Common adapters you'll see:
- PrismaAdapter — Connects to your database via Prisma ORM. Most popular choice.
- DrizzleAdapter — For Drizzle ORM users. Lighter weight than Prisma.
- SupabaseAdapter — For Supabase projects (though Supabase has its own built-in auth you might prefer).
What AI Gets Wrong About NextAuth
Here's where the real value is. These are the issues you'll actually hit when using AI-generated NextAuth code — and AI's suggested fixes are often wrong or incomplete.
1. Missing or Wrong Environment Variables
What happens: Your app crashes on startup, or the sign-in button redirects to an error page. The console shows something like Error: Missing NEXTAUTH_SECRET or OAuth Error: invalid_client.
Why AI gets this wrong: AI generates the code that uses environment variables but can't set them for you. It writes process.env.GOOGLE_CLIENT_ID and moves on, assuming you'll fill in the values. Often it doesn't even mention that you need to create a Google Cloud project and generate OAuth credentials.
The fix:
- Create a
.env.localfile in your project root (not inside/appor/src) - Get your Google credentials from Google Cloud Console → Credentials
- Generate a secret: run
openssl rand -base64 32in your terminal - Set
NEXTAUTH_URL=http://localhost:3000for development
2. Wrong Callback URLs
What happens: You click "Sign in with Google," the Google popup appears, you pick your account, then you get a "redirect_uri_mismatch" error or land on a NextAuth error page.
Why AI gets this wrong: The callback URL in your Google Cloud Console must exactly match what NextAuth expects. AI doesn't know your deployment URL, doesn't set up the Google Console for you, and often gives you the wrong format.
The fix: In your Google Cloud Console, add these as authorized redirect URIs:
# For development:
http://localhost:3000/api/auth/callback/google
# For production:
https://yourdomain.com/api/auth/callback/google
The pattern is always {your-url}/api/auth/callback/{provider-name}. Get this wrong by even one character and login fails silently.
3. Session Not Available (The "null" Problem)
What happens: useSession() always returns null or undefined even after logging in. Your dashboard shows "Welcome, undefined" or crashes completely.
Why AI gets this wrong: Usually one of three issues:
- Missing
SessionProvider— AI forgot to wrap the app in the provider, or wrapped it in the wrong place. It must wrap your entire app in the root layout. - Missing
"use client"—useSession()is a React hook and only works in client components. If the component doesn't have"use client"at the top, it silently fails. - Server Component confusion — In Next.js App Router, pages are server components by default. AI sometimes uses
useSession()in a server component (doesn't work). For server components, you needgetServerSession()instead.
The fix: Check three things: (1) SessionProvider wraps your app in layout.tsx, (2) any component using useSession() has "use client" at the top, (3) server components use getServerSession(authOptions) instead.
4. Adapter and Database Confusion
What happens: Your AI generates Prisma schema files, database migration commands, and adapter code — then your app crashes because you don't have a database set up.
Why AI gets this wrong: When you say "add login," AI often assumes you want the full enterprise setup with persistent user storage. For many apps, especially MVPs and prototypes, you just want sign-in to work — you don't need user records in a database.
The fix: If you don't need a database:
- Remove the adapter import and config from your NextAuth options
- Delete any generated Prisma schema or migration files related to auth
- Make sure
session: { strategy: "jwt" }is set (or just omit it — JWT is the default)
You'll know you need a database adapter when you need features like "link multiple providers to one account" or "show a list of all registered users in an admin panel."
5. NextAuth v4 vs v5 Confusion
What happens: You follow AI's instructions, but the imports don't resolve, or the configuration object has the wrong shape. The docs you find online contradict what AI generated.
Why AI gets this wrong: NextAuth has two major versions in active use. v4 uses next-auth and the Pages Router pattern (pages/api/auth/[...nextauth].ts). v5 uses @auth/...packages and the App Router pattern. AI frequently mixes syntax from both versions in the same project.
The fix: Check your package.json. If you see "next-auth": "^4.x", use v4 docs and patterns. If you see "next-auth": "^5.x" or "@auth/core", use v5 patterns. Don't mix them. If your AI generated a mix, tell it: "I'm using NextAuth v4 with the App Router. Regenerate all auth code for that specific version."
How to Debug NextAuth Issues
When login isn't working, here's a systematic approach instead of throwing prompts at the wall. These steps work whether you're debugging AI-generated code or something you wrote yourself.
Step 1: Enable Debug Mode
Add debug: true to your NextAuth config:
const handler = NextAuth({
providers: [...],
debug: true, // Add this line
secret: process.env.NEXTAUTH_SECRET,
})
This prints detailed logs to your server console showing exactly what's happening during the auth flow — which provider is being used, what tokens are being exchanged, and where failures occur.
Step 2: Check the Auth API Directly
Open these URLs in your browser to verify NextAuth is running:
http://localhost:3000/api/auth/providers— Should show your configured providers as JSONhttp://localhost:3000/api/auth/session— Should show your current session (or{}if not logged in)http://localhost:3000/api/auth/signin— Should show the NextAuth sign-in page
If /api/auth/providers returns a 404, your route handler file is in the wrong location or named incorrectly.
Step 3: Verify Environment Variables Are Loading
// Temporary debug — remove before deploying!
console.log("GOOGLE_CLIENT_ID:", process.env.GOOGLE_CLIENT_ID ? "SET" : "MISSING")
console.log("NEXTAUTH_SECRET:", process.env.NEXTAUTH_SECRET ? "SET" : "MISSING")
console.log("NEXTAUTH_URL:", process.env.NEXTAUTH_URL)
Add this temporarily in your NextAuth route file. If any show "MISSING," your .env.local file isn't being read. Common cause: the file is in the wrong directory (it must be in the project root, same level as package.json).
Step 4: Check the Browser Console and Network Tab
Open your browser's developer tools (F12). Look at:
- Console tab: Any errors about CSRF tokens, cookies, or session
- Network tab: Filter by "auth" — look for failed requests (red entries). Click them to see the error response.
- Application tab → Cookies: You should see a
next-auth.session-tokencookie after signing in. If it's missing, the sign-in flow didn't complete.
When to Tell AI vs. Fix Yourself
Some things you should fix manually:
- Environment variables — AI can't see your
.env.localfile. Set these yourself. - Google Cloud Console setup — AI can't click buttons for you. Follow Google's OAuth 2.0 setup guide.
- Callback URL mismatches — Copy your exact URL from the browser and paste it into the console. Don't let AI guess.
Some things AI is actually good at fixing:
- Version mismatches — Tell it your exact NextAuth version and ask for correct syntax
- Adding new providers — "Add GitHub login alongside my existing Google login"
- Session customization — "Add the user's role to the session data"
- Protected route patterns — "Make the /admin route require authentication using middleware"
The Complete Picture: NextAuth as a Building Security System
If it helps to see how all the pieces fit together, here's the whole system mapped to building security:
| NextAuth Concept | Building Equivalent | What It Does |
|---|---|---|
| Providers | Accepted forms of ID | Google badge, GitHub badge, email PIN |
| Route Handler | Security desk | Processes all check-ins and check-outs |
| SessionProvider | Badge reader system | Installed building-wide so any room can verify access |
| useSession() | Individual badge reader | Each room checks: "Do you have a valid badge?" |
| Callbacks | Custom security rules | "Building B workers get an extra sticker on their badge" |
| JWT Token | Badge on a lanyard | You carry your credentials with you |
| Database Session | Central badge registry | Security office can remotely deactivate any badge |
| NEXTAUTH_SECRET | Master encryption key | Makes badges impossible to forge |
| Adapter | Filing cabinet connection | Links the security desk to a permanent employee database |
What to Learn Next
Now that you understand what NextAuth.js does and how its pieces fit together, here's your learning path:
- What Is Authentication? — The broader concepts behind login systems, if you want to understand the fundamentals that NextAuth builds on.
- What Is OAuth? — Deep dive into how "Sign in with Google" actually works behind the scenes. NextAuth uses OAuth 2.0 under the hood.
- What Is JWT? — Understanding JSON Web Tokens, the default session storage that NextAuth uses.
- What Are Environment Variables? — Essential knowledge for managing the secrets and configuration that NextAuth depends on.
- How to Debug AI-Generated Code — A systematic approach to fixing the inevitable issues that come up when AI builds your features.
Frequently Asked Questions
What is the difference between NextAuth.js and Auth.js?
They're the same project. NextAuth.js was the original name when it only worked with Next.js. The team rebranded to Auth.js as they expanded to support other frameworks like SvelteKit and SolidStart. If your AI generates next-auth imports, that's the older v4 package. If it generates @auth/... imports, that's the newer v5. Both work, but the configuration syntax differs slightly.
Do I need a database for NextAuth.js to work?
No. By default, NextAuth.js uses JWT (JSON Web Tokens) stored in cookies — no database required. You only need a database if you want to store user accounts persistently, manage multiple sessions, or link multiple OAuth providers to one user. AI often generates database adapter code even when you don't need it, which adds unnecessary complexity.
Why does my NextAuth Google login redirect to an error page?
The most common cause is mismatched callback URLs. In your Google Cloud Console, the authorized redirect URI must exactly match your NextAuth callback URL — typically http://localhost:3000/api/auth/callback/google for development. Other common causes: missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET environment variables, or forgetting to set NEXTAUTH_URL.
What is the NEXTAUTH_SECRET and why do I need it?
NEXTAUTH_SECRET is a random string used to encrypt session tokens and cookies. Without it, NextAuth can't securely sign your sessions — it's like a building's master key for the security system. Generate one by running openssl rand -base64 32 in your terminal and add it to your .env.local file. In production, this is required — your app will crash without it.
Can I use NextAuth.js with providers other than Google and GitHub?
Yes. NextAuth.js supports 80+ providers including Discord, Apple, Twitter, Facebook, LinkedIn, Spotify, and many more. It also supports email/password login via the Credentials provider and magic link login via the Email provider. AI tends to default to Google and GitHub because they're the most common, but switching providers is usually just swapping one provider config block for another.