Topic 13 of 47
Authentication with Auth.js
Overview
Auth.js (formerly NextAuth.js) is the standard authentication library for Next.js — supporting OAuth providers (Google, GitHub), credentials, email magic links, and session management with minimal configuration.
Syntax
typescript
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID!,
clientSecret: process.env.GOOGLE_SECRET!,
}),
CredentialsProvider({
async authorize(credentials) {
const user = await db.users.findByEmail(credentials.email);
if (!user || !bcrypt.compare(credentials.password, user.password)) {
return null; // null = authentication failed
}
return user; // return user to create session
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) token.role = user.role; // add custom fields
return token;
},
async session({ session, token }) {
session.user.role = token.role; // expose in session
return session;
},
},
});
export { handler as GET, handler as POST };Common Pitfalls
- Always validate sessions server-side in Server Components — client-side session checks can be bypassed.
- JWT strategy (default) stores data in a cookie; database strategy stores in DB. JWT is stateless, database allows immediate revocation.
- Interview tip: Auth.js v5 introduces a unified auth() function that works in both Server Components and middleware — replacing auth().
Real-World Example
Protected route with role-based access using Auth.js
example
typescript
// lib/auth.ts — shared auth config
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
export async function requireAuth() {
const session = await auth();
if (!session) redirect("/login");
return session;
}
export async function requireAdmin() {
const session = await requireAuth();
if (session.user.role !== "admin") redirect("/unauthorized");
return session;
}
// app/admin/page.tsx — admin-only page
export default async function AdminPage() {
const session = await requireAdmin(); // redirects if not admin
const stats = await getAdminStats();
return <AdminDashboard stats={stats} user={session.user} />;
}
// Client component — check session
"use client";
import { useSession, signIn, signOut } from "next-auth/react";
function AuthButton() {
const { data: session, status } = useSession();
if (status === "loading") return <Spinner />;
if (session) return <button onClick={() => signOut()}>Sign Out</button>;
return <button onClick={() => signIn("google")}>Sign in with Google</button>;
}