Next.js Authentication
Authentication is how your app verifies who a user is. It answers the question: "Are you who you say you are?" Once verified, authorization answers: "What are you allowed to do?" Next.js does not have authentication built in, but it integrates cleanly with popular libraries that handle the heavy lifting.
Authentication Concepts
AUTHENTICATION → Verify identity ("Are you Alice?")
Login with email/password, Google, GitHub, etc.
AUTHORIZATION → Verify permission ("Can Alice access /admin?")
Check role, ownership, or subscription level
SESSION → Proof of login stored after authentication
Stored in a cookie or database, expires after time
TOKEN → A signed string proving identity
JWT (JSON Web Token) is the most common type
The Recommended Library: NextAuth.js (Auth.js)
NextAuth.js — now called Auth.js — is the most popular authentication library for Next.js. It handles sessions, cookies, database connections, and OAuth providers like Google and GitHub with minimal code.
npm install next-auth@beta
Setting Up Auth.js
Create an auth.js file at the root of your project to configure providers and options.
// auth.js
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
});
Then create the API route that handles all authentication requests:
// app/api/auth/[...nextauth]/route.js
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
Diagram: OAuth Login Flow
USER CLICKS "Sign in with Google"
↓
Next.js redirects to Google's login page
↓
User enters Google credentials on Google's site
↓
Google redirects back to your app with a code
↓
Auth.js exchanges code for user info (name, email, photo)
↓
Auth.js creates a session and sets a cookie
↓
User is logged in, browser stores session cookie
Reading the Session in Server Components
Use the auth() function to read the current session on the server. It returns null if the user is not logged in.
// app/dashboard/page.js
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session) {
redirect('/login');
}
return (
<main>
<h1>Welcome, {session.user.name}</h1>
<p>Email: {session.user.email}</p>
</main>
);
}
Protecting Routes with Middleware
Use Auth.js with Next.js middleware to protect entire sections of your site. Unauthenticated users get redirected to the login page before any page code runs.
// middleware.js
import { auth } from '@/auth';
export default auth((request) => {
const isLoggedIn = !!request.auth;
const isProtected = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtected && !isLoggedIn) {
return Response.redirect(new URL('/login', request.url));
}
});
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*'],
};
Sign In and Sign Out Buttons
// app/components/AuthButtons.js
import { signIn, signOut } from '@/auth';
export function SignInButton() {
return (
<form
action={async () => {
'use server';
await signIn('github');
}}
>
<button type="submit">Sign in with GitHub</button>
</form>
);
}
export function SignOutButton() {
return (
<form
action={async () => {
'use server';
await signOut();
}}
>
<button type="submit">Sign out</button>
</form>
);
}
Email and Password Authentication
For username and password login without OAuth, use the Credentials provider. Validate credentials yourself, then return the user object if the login succeeds.
import Credentials from 'next-auth/providers/credentials';
import { verifyPassword } from '@/lib/password';
import { db } from '@/lib/db';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Credentials({
async authorize(credentials) {
const user = await db.users.findOne({ email: credentials.email });
if (!user) return null;
const passwordValid = await verifyPassword(
credentials.password,
user.hashedPassword
);
if (!passwordValid) return null;
return { id: user.id, name: user.name, email: user.email };
},
}),
],
});
Session Data Shape
session = {
user: {
name: "Alice Johnson",
email: "alice@example.com",
image: "https://avatars.githubusercontent.com/u/123"
},
expires: "2025-12-31T00:00:00.000Z"
}
Access in components:
session.user.name → "Alice Johnson"
session.user.email → "alice@example.com"
session.user.image → URL of profile photo
Diagram: Where Authentication Happens
LAYER TOOL PURPOSE ────────────────────────────────────────────────────── Middleware auth() from Auth.js Block unauthenticated routes fast Server Component auth() Read session, redirect if needed Server Action auth() Confirm user before DB mutation Client Component useSession() Show/hide UI based on login state API Route auth() Protect API endpoints
Key Takeaway
Use Auth.js to handle authentication in Next.js. Configure OAuth providers like Google and GitHub with just a client ID and secret. Read the session with auth() in server components and server actions. Protect entire route segments using middleware so unauthenticated users never reach protected pages. For email and password login, use the Credentials provider and validate users yourself.
