NextAuth.js Integration for Website
NextAuth.js (Auth.js v5) is an authentication library specifically for Next.js. Supports 50+ OAuth providers, email (magic link), credentials (login/password), WebAuthn. Sessions via JWT or database. Version v5 (Auth.js) works with Edge Runtime and App Router.
Auth.js v5 Installation
npm install next-auth@beta
# or for stable v4:
npm install next-auth
Configuration
// auth.ts (project root)
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';
import bcrypt from 'bcrypt';
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma), // database sessions
providers: [
Google({
clientId: process.env.AUTH_GOOGLE_ID!,
clientSecret: process.env.AUTH_GOOGLE_SECRET!,
}),
GitHub({
clientId: process.env.AUTH_GITHUB_ID!,
clientSecret: process.env.AUTH_GITHUB_SECRET!,
}),
Credentials({
credentials: {
email: { type: 'email' },
password: { type: 'password' },
},
async authorize(credentials) {
const user = await prisma.user.findUnique({
where: { email: credentials.email as string },
});
if (!user?.password) return null;
const valid = await bcrypt.compare(
credentials.password as string,
user.password
);
return valid ? user : null;
},
}),
],
session: { strategy: 'database' }, // or 'jwt' without adapter
callbacks: {
async session({ session, user }) {
// Add custom fields to session
session.user.id = user.id;
session.user.role = user.role;
return session;
},
},
pages: {
signIn: '/login',
error: '/auth/error',
verifyRequest: '/auth/verify', // for magic link
},
});
Route Handler
// src/app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
Middleware for Route Protection
// middleware.ts
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
export default auth((req) => {
if (!req.auth) {
const loginUrl = new URL('/login', req.url);
loginUrl.searchParams.set('callbackUrl', req.url);
return NextResponse.redirect(loginUrl);
}
});
export const config = {
matcher: [
'/dashboard/:path*',
'/settings/:path*',
'/api/protected/:path*',
],
};
Server Components — Get Session
// app/dashboard/page.tsx
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session) redirect('/login');
return (
<div>
<h1>Hello, {session.user.name}!</h1>
<p>Email: {session.user.email}</p>
</div>
);
}
Client Components — useSession Hook
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export function Header() {
const { data: session, status } = useSession();
if (status === 'loading') return <Skeleton />;
if (!session) {
return <button onClick={() => signIn('google')}>Sign In</button>;
}
return (
<div className="flex items-center gap-4">
<img src={session.user.image ?? ''} alt="" className="w-8 h-8 rounded-full" />
<span>{session.user.name}</span>
<button onClick={() => signOut({ callbackUrl: '/' })}>Sign Out</button>
</div>
);
}
Prisma Schema for Database Sessions
// schema.prisma
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
password String? // for Credentials provider
role String @default("user")
accounts Account[]
sessions Session[]
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
Email Provider (Magic Link)
import Resend from 'next-auth/providers/resend';
// or Nodemailer, SendGrid, Postmark
providers: [
Resend({
from: '[email protected]',
apiKey: process.env.AUTH_RESEND_KEY,
// Custom email template:
async sendVerificationRequest({ identifier: email, url, provider }) {
const resend = new Resend(provider.apiKey);
await resend.emails.send({
from: provider.from,
to: email,
subject: 'Sign in to your account',
html: `<a href="${url}">Click here to sign in</a>`,
});
},
}),
]
TypeScript — Extend Types
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: 'admin' | 'editor' | 'user';
} & DefaultSession['user'];
}
interface User {
role: 'admin' | 'editor' | 'user';
}
}
Server Actions with Authentication
// app/actions/article.ts
'use server';
import { auth } from '@/auth';
export async function createArticle(data: CreateArticleData) {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
if (session.user.role !== 'editor' && session.user.role !== 'admin') {
throw new Error('Forbidden');
}
return prisma.article.create({
data: { ...data, authorId: session.user.id },
});
}
Timeline
NextAuth.js v5, Google + GitHub providers, Credentials, database sessions via Prisma, route protection middleware: 1–2 days. With magic link, custom login pages, extended types, Server Actions auth: 3–4 days.







