NextAuth.js
Authentication Library
NextAuth.js
Overview
NextAuth.js is a complete authentication solution for Next.js applications, supporting multiple authentication methods including OAuth, email, and credential authentication, providing a secure and easy-to-use authentication system.
Details
NextAuth.js (now also known as Auth.js) is an authentication library specifically designed for Next.js applications. As of 2024, it has been significantly updated as version 5.0 (Auth.js v5), providing more flexible and powerful authentication features.
It has built-in support for over 50 OAuth providers, making integration with major services like Google, GitHub, Facebook, Twitter, Microsoft, Apple, Discord, and Spotify easy. It also supports email/password authentication, magic link authentication, and custom credential authentication, catering to various authentication needs.
For framework integration, it supports both Next.js App Router (v13+) and Pages Router, and is also usable with other frameworks like React, SvelteKit, Express, Qwik, and SolidStart. For database integration, it provides major ORM and database adapters including Prisma, Drizzle, TypeORM, Supabase, PlanetScale, and Mongoose.
Security features include CSRF protection, JWT token support, session management, refresh token rotation, multi-factor authentication (MFA), and Passkey/WebAuthn support, meeting modern security requirements. It also supports TypeScript fully, server-side rendering (SSR), and edge runtime support, aligning with the latest web development trends.
Auth.js v5 features a more modular design, making customization easier. The new Unified API enables code sharing across different frameworks, significantly improving the developer experience. It also includes official support for WebAuth/Passkey authentication, improved error handling, and performance optimizations.
Pros and Cons
Pros
- Comprehensive Provider Support: Built-in support for 50+ OAuth providers
- Framework Integration: Full Next.js App Router support, other frameworks supported
- Easy Setup: Build authentication systems with minimal configuration
- Security First: CSRF protection, JWT, session management included by default
- Full TypeScript Support: Type-safe with excellent developer experience
- Database Flexibility: Provides major ORM and database adapters
- Modern Features: WebAuthn/Passkey, MFA, edge runtime support
- Active Community: Rich documentation and community support
Cons
- Next.js Dependency: Some features may be limited in other frameworks
- Configuration Complexity: Learning curve required for advanced customization
- Bundle Size: Bundle size can be large due to comprehensive features
- Update Impact: Breaking changes in migration from v4 to v5
- Provider Dependency: Many features depend on external services
- Performance: May have lower performance compared to lightweight libraries due to feature richness
- Customization Limits: May have limitations with highly custom authentication flows
Key Links
- NextAuth.js Official Site
- Auth.js Documentation
- GitHub Repository
- Providers List
- Adapters List
- Sample App
- Community
- Migration Guide
Code Examples
Basic Setup (App Router - v5)
// app/auth.ts
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import GitHub from "next-auth/providers/github"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
],
pages: {
signIn: '/auth/signin',
error: '/auth/error',
},
callbacks: {
authorized: async ({ auth }) => {
// Check if user is logged in
return !!auth
},
},
})
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth" // Reference to auth.ts above
export const { GET, POST } = handlers
Middleware Configuration
// middleware.ts
import { auth } from "@/auth"
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname !== '/login') {
const newUrl = new URL('/login', req.nextUrl.origin)
return Response.redirect(newUrl)
}
})
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
Client-side Usage
// app/components/SignInButton.tsx
'use client'
import { signIn, signOut, useSession } from "next-auth/react"
export function SignInButton() {
const { data: session, status } = useSession()
if (status === "loading") return <p>Loading...</p>
if (session) {
return (
<div>
<p>Signed in as: {session.user?.email}</p>
<button onClick={() => signOut()}>Sign Out</button>
</div>
)
}
return (
<div>
<button onClick={() => signIn()}>Sign In</button>
<button onClick={() => signIn('google')}>Sign in with Google</button>
<button onClick={() => signIn('github')}>Sign in with GitHub</button>
</div>
)
}
Session Provider Setup
// 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,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
)
}
Server-side Session Retrieval
// app/profile/page.tsx
import { auth } from "@/auth"
import { redirect } from "next/navigation"
export default async function ProfilePage() {
const session = await auth()
if (!session) {
redirect('/api/auth/signin')
}
return (
<div>
<h1>Profile</h1>
<p>Welcome, {session.user?.name}!</p>
<p>Email: {session.user?.email}</p>
<img src={session.user?.image} alt="Profile image" />
</div>
)
}
Credentials Authentication (Custom Authentication)
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import { compare } from "bcryptjs"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Credentials({
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null
}
// Search for user in database
const user = await getUserByEmail(credentials.email)
if (!user) {
return null
}
// Verify password
const isPasswordValid = await compare(credentials.password, user.password)
if (!isPasswordValid) {
return null
}
return {
id: user.id,
email: user.email,
name: user.name,
}
}
})
],
})
Database Setup (Prisma)
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
// Provider configuration
],
session: {
strategy: "database",
},
})
JWT Configuration
import NextAuth from "next-auth"
import type { JWT } from "next-auth/jwt"
export const { handlers, auth, signIn, signOut } = NextAuth({
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
}
return token
},
async session({ session, token }) {
if (token) {
session.user.id = token.id as string
}
return session
},
},
})
Custom Sign-in Page
// app/auth/signin/page.tsx
import { signIn, getProviders } from "next-auth/react"
import { getServerSession } from "next-auth"
export default async function SignIn() {
const providers = await getProviders()
return (
<div className="signin-page">
<h1>Sign In</h1>
{Object.values(providers ?? {}).map((provider) => (
<div key={provider.name}>
<button
onClick={() => signIn(provider.id)}
className="provider-button"
>
Sign in with {provider.name}
</button>
</div>
))}
</div>
)
}
API Protection
// app/api/protected/route.ts
import { auth } from "@/auth"
import { NextResponse } from "next/server"
export async function GET() {
const session = await auth()
if (!session) {
return new NextResponse("Authentication required", { status: 401 })
}
return NextResponse.json({
message: "Protected data",
user: session.user,
})
}
Custom Callbacks
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
// Provider configuration
],
callbacks: {
async signIn({ user, account, profile }) {
// Sign-in processing
if (account?.provider === "google") {
return profile?.email_verified && profile?.email?.endsWith("@example.com")
}
return true
},
async redirect({ url, baseUrl }) {
// Custom redirect destination
if (url.startsWith("/")) return `${baseUrl}${url}`
else if (new URL(url).origin === baseUrl) return url
return baseUrl
},
async session({ session, token }) {
// Customize session information
session.user.id = token.sub!
return session
},
async jwt({ token, user, account }) {
// Customize JWT token
if (user) {
token.id = user.id
}
return token
},
},
})
Email Authentication Setup
import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
}),
],
})
Custom Error Handling
// app/auth/error/page.tsx
'use client'
import { useSearchParams } from 'next/navigation'
export default function AuthError() {
const searchParams = useSearchParams()
const error = searchParams.get('error')
return (
<div className="error-page">
<h1>Authentication Error</h1>
{error === 'Configuration' && (
<p>There is a problem with the server configuration.</p>
)}
{error === 'AccessDenied' && (
<p>Access was denied.</p>
)}
{error === 'Verification' && (
<p>The verification token is expired or invalid.</p>
)}
<a href="/api/auth/signin">Sign in again</a>
</div>
)
}