Next.js Authentication — NextAuth.js v5 Guide
Advertisement
Next.js Authentication — NextAuth.js v5 Guide
NextAuth.js v5 provides secure, flexible authentication for Next.js applications with support for multiple authentication methods.
- Next.js Authentication — NextAuth.js v5 Guide
- Installation
- Basic Setup
- GitHub OAuth
- Multiple Providers
- Login Page
- Protected Pages
- Client-Side Session
- Middleware Protection
- Magic Links
- User Creation
- FAQ
Installation
npm install next-auth
Basic Setup
Create auth configuration:
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
const handler = NextAuth({
providers: [
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null
}
const user = await db.users.findUnique({
where: { email: credentials.email }
})
if (!user) return null
const isPasswordValid = await bcrypt.compare(
credentials.password,
user.password
)
if (!isPasswordValid) return null
return {
id: user.id,
email: user.email,
name: user.name
}
}
})
],
pages: {
signIn: '/login',
error: '/auth/error',
}
})
export { handler as GET, handler as POST }
GitHub OAuth
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
const handler = NextAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
})
]
})
export { handler as GET, handler as POST }
Environment variables:
GITHUB_ID=your_github_id
GITHUB_SECRET=your_github_secret
NEXTAUTH_SECRET=your_secret_key
Multiple Providers
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
import Credentials from 'next-auth/providers/credentials'
const handler = NextAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
Google({
clientId: process.env.GOOGLE_ID!,
clientSecret: process.env.GOOGLE_SECRET!,
}),
Credentials({
// credentials config
})
],
pages: {
signIn: '/login',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
}
return token
},
async session({ session, token }) {
session.user.id = token.id as string
return session
}
}
})
export { handler as GET, handler as POST }
Login Page
// app/login/page.tsx
'use client'
import { signIn } from 'next-auth/react'
import { useState } from 'react'
export default function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
async function handleSubmit(e) {
e.preventDefault()
const result = await signIn('credentials', {
email,
password,
redirect: false
})
if (!result?.ok) {
setError('Invalid credentials')
}
}
return (
<div className="max-w-md mx-auto mt-8">
<form onSubmit={handleSubmit} className="space-y-4">
{error && <p className="text-red-600">{error}</p>}
<div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
className="w-full px-4 py-2 border rounded"
/>
</div>
<div>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
className="w-full px-4 py-2 border rounded"
/>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 rounded"
>
Sign In
</button>
</form>
<div className="mt-4 space-y-2">
<button
onClick={() => signIn('github')}
className="w-full bg-gray-900 text-white py-2 rounded"
>
Sign in with GitHub
</button>
<button
onClick={() => signIn('google')}
className="w-full bg-blue-500 text-white py-2 rounded"
>
Sign in with Google
</button>
</div>
</div>
)
}
Protected Pages
// app/dashboard/page.tsx
import { redirect } from 'next/navigation'
import { getServerSession } from 'next-auth'
export default async function DashboardPage() {
const session = await getServerSession()
if (!session?.user) {
redirect('/login')
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<p>Email: {session.user.email}</p>
</div>
)
}
Client-Side Session
// app/components/user-menu.tsx
'use client'
import { useSession, signOut } from 'next-auth/react'
export function UserMenu() {
const { data: session } = useSession()
if (!session?.user) {
return <a href="/login">Sign In</a>
}
return (
<div className="flex items-center gap-4">
<span>{session.user.email}</span>
<button onClick={() => signOut()}>Sign Out</button>
</div>
)
}
Middleware Protection
// middleware.ts
import { auth } from '@/auth'
export default auth(function middleware(req) {
// req.auth will have session info
})
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
Magic Links
import NextAuth from 'next-auth'
import Email from 'next-auth/providers/email'
const handler = NextAuth({
providers: [
Email({
server: process.env.EMAIL_SERVER_URL,
from: process.env.EMAIL_FROM
})
]
})
export { handler as GET, handler as POST }
User Creation
// app/register/page.tsx
'use client'
import { useState } from 'react'
export default function RegisterPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [name, setName] = useState('')
async function handleSubmit(e) {
e.preventDefault()
const response = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify({ email, password, name })
})
if (response.ok) {
window.location.href = '/login'
}
}
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto mt-8 space-y-4">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Full Name"
className="w-full px-4 py-2 border rounded"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
className="w-full px-4 py-2 border rounded"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
className="w-full px-4 py-2 border rounded"
/>
<button type="submit" className="w-full bg-blue-600 text-white py-2 rounded">
Register
</button>
</form>
)
}
// app/api/register/route.ts
import bcrypt from 'bcryptjs'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const { email, password, name } = await request.json()
const existingUser = await db.users.findUnique({ where: { email } })
if (existingUser) {
return NextResponse.json(
{ error: 'User exists' },
{ status: 400 }
)
}
const hashedPassword = await bcrypt.hash(password, 10)
const user = await db.users.create({
data: { email, password: hashedPassword, name }
})
return NextResponse.json(user)
}
FAQ
Q: How do I store custom user fields? A: Create a custom User model in your database and include it in the callbacks.
Q: Should I use JWT or database sessions? A: JWT for stateless APIs, database sessions for traditional web apps. NextAuth.js handles both.
Q: How do I refresh sessions? A: NextAuth.js handles session refresh automatically. Manual refresh uses useSession({ required: true }).
NextAuth.js simplifies authentication implementation in Next.js applications.
Advertisement