1. Pengenalan NextAuth.js
NextAuth.js (sekarang bernama Auth.js) adalah library autentikasi open-source yang dirancang khusus untuk aplikasi Next.js. Library ini menyediakan solusi autentikasi yang lengkap β mulai dari OAuth social login (Google, GitHub, Facebook, dll.), credentials-based login, hingga manajemen sesi β dengan konfigurasi minimal.
Dibuat oleh Iain Collins, NextAuth.js telah menjadi standar de facto untuk autentikasi di ekosistem Next.js. Dengan lebih dari 20.000+ stars di GitHub dan digunakan oleh ribuan aplikasi production.
Fitur Utama NextAuth.js
| Fitur | Penjelasan |
|---|---|
| OAuth Providers | Dukungan 80+ provider: Google, GitHub, Facebook, Twitter, Apple, dll. |
| Email/Password | Support credentials-based login (username/password) |
| Magic Link | Login via email tanpa password (passwordless) |
| Session Strategy | JWT (default) atau database sessions |
| Database Adapters | Prisma, Drizzle, MongoDB, Fauna, Supabase, dll. |
| Callbacks | Kustomisasi alur autentikasi: sign-in, session, JWT, redirect |
| CSRF Protection | Perlindungan CSRF built-in |
| Secure Cookies | HttpOnly, Secure, SameSite cookies otomatis |
Arsitektur NextAuth.js
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENT (Browser) β
β β
β ββββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β β signIn() β β useSession() β β signOut() β β
β β Login/Logout β β Get Session β β Logout β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬βββββββ β
β β β β β
β ββββββββββββββββββββΌββββββββββββββββββββ β
β β β
β ββββββββββΌβββββββββ β
β β Secure Cookies β β
β β (HttpOnly, β β
β β SameSite) β β
β ββββββββββ¬βββββββββ β
ββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ
β Next.js Server β
β βββββββΌββββββ β
β β NextAuth β β
β β API Route β β
β βββββββ¬ββββββ β
β β β
β ββββββββββββββββββββββΌβββββββββββββββββββββ β
β ββββββΌββββββ βββββββββββΌβββββββββ ββββββββΌβββββββ β
β β OAuth β β Callbacks β β Database β β
β β Providersβ β (jwt, session, β β Adapter β β
β β (Google, β β signIn, redirectβ β (Prisma, β β
β β GitHub) β β ) β β MongoDB) β β
β ββββββββββββ ββββββββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Instalasi & Setup
Mari kita mulai dengan menginstal dan mengkonfigurasi NextAuth.js di proyek Next.js. Pastikan Anda sudah memiliki proyek Next.js yang berjalan.
Instalasi
# Instal NextAuth.js npm install next-auth # Untuk database adapter (opsional, jika menggunakan database) npm install @prisma/client prisma @auth/prisma-adapter # Untuk Next.js App Router (v13+) # Sudah termasuk dalam next-auth@latest
Konfigurasi Environment Variables
# NextAuth.js NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=rahasia-super-panjang-dan-acak-untuk-produksi # Google OAuth (dapatkan dari console.cloud.google.com) GOOGLE_CLIENT_ID=xxxxxxxxxxxx-xxxxxxxxxxxx.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxxx # GitHub OAuth (dapatkan dari github.com/settings/developers) GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Database URL (jika menggunakan Prisma) DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
NEXTAUTH_SECRET adalah string acak yang panjang (minimal 32 karakter). Ini digunakan untuk mengenkripsi JWT tokens dan cookies. Di production, gunakan openssl rand -base64 32 untuk generate secret yang aman.
API Route β NextAuth Configuration
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';
import CredentialsProvider from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';
import bcrypt from 'bcryptjs';
const handler = NextAuth({
// Adapter untuk menyimpan user/session di database
adapter: PrismaAdapter(prisma),
// Authentication providers
providers: [
// Google OAuth
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
},
},
}),
// GitHub OAuth
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
// Credentials (username/password)
CredentialsProvider({
name: 'Credentials',
credentials: {
email: {
label: 'Email',
type: 'email',
placeholder: 'email@kamu.com',
},
password: {
label: 'Password',
type: 'password',
},
},
async authorize(credentials) {
// Cari user di database
const user = await prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user || !user.hashedPassword) {
throw new Error('Email atau password salah');
}
// Verifikasi password
const isValid = await bcrypt.compare(
credentials.password,
user.hashedPassword
);
if (!isValid) {
throw new Error('Email atau password salah');
}
// Kembalikan user object (akan masuk ke jwt callback)
return {
id: user.id,
name: user.name,
email: user.email,
image: user.image,
role: user.role,
};
},
}),
],
// Session strategy
session: {
strategy: 'jwt', // 'jwt' atau 'database'
maxAge: 30 * 24 * 60 * 60, // 30 hari
updateAge: 24 * 60 * 60, // 24 jam
},
// Halaman custom
pages: {
signIn: '/auth/login', // Custom login page
signOut: '/auth/logout', // Custom logout page
error: '/auth/error', // Error page
newUser: '/auth/register', // Redirect new user setelah sign up
},
// Callbacks
callbacks: {
// 1. Sign In Callback β kontrol apakah user boleh login
async signIn({ user, account, profile }) {
if (account.provider === 'google') {
// Contoh: hanya izinkan email tertentu
// return profile.email.endsWith('@beebane.com');
return true;
}
if (account.provider === 'credentials') {
return !!user; // Pastikan authorize() mengembalikan user
}
return true;
},
// 2. JWT Callback β kustomisasi isi JWT token
async jwt({ token, user, account, profile, trigger }) {
// Saat pertama kali sign in
if (user) {
token.id = user.id;
token.role = user.role || 'user';
}
// Refresh token saat session di-update
if (trigger === 'update') {
const refreshedUser = await prisma.user.findUnique({
where: { id: token.id },
});
token.role = refreshedUser.role;
}
return token;
},
// 3. Session Callback β kustomisasi data yang dikirim ke client
async session({ session, token }) {
if (token) {
session.user.id = token.id;
session.user.role = token.role;
}
return session;
},
// 4. Redirect Callback β kustomisasi URL redirect
async redirect({ url, baseUrl }) {
// Redirect ke halaman yang diminta setelah login
if (url.startsWith('/')) return `${baseUrl}${url}`;
if (new URL(url).origin === baseUrl) return url;
return baseUrl;
},
},
// Events β hook untuk berbagai tahap autentikasi
events: {
async signIn({ user, account, profile }) {
console.log(`User ${user.email} logged in via ${account.provider}`);
},
async signOut({ token }) {
console.log(`User ${token.email} logged out`);
},
async createUser({ user }) {
console.log(`New user created: ${user.email}`);
// Kirim email selamat datang, dll.
},
async linkAccount({ user, account }) {
console.log(`Account ${account.provider} linked to ${user.email}`);
},
},
// Debug mode (matikan di production!)
debug: process.env.NODE_ENV === 'development',
});
export { handler as GET, handler as POST };
Session Provider di Layout
import { SessionProvider } from '@/components/SessionProvider';
import './globals.css';
export const metadata = {
title: 'Aplikasi Saya',
description: 'Dengan NextAuth.js authentication',
};
export default function RootLayout({ children }) {
return (
<html lang="id">
<body>
<SessionProvider>
{children}
</SessionProvider>
</body>
</html>
);
}
'use client';
import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react';
export function SessionProvider({ children }) {
return (
<NextAuthSessionProvider>
{children}
</NextAuthSessionProvider>
);
}
3. Authentication Providers
NextAuth.js mendukung berbagai authentication providers. Berikut adalah beberapa yang paling umum digunakan:
Google OAuth Provider
Langkah-langkah setup Google OAuth: 1. Buka https://console.cloud.google.com 2. Buat project baru atau pilih project yang ada 3. Aktifkan "Google+ API" di Library 4. Buka "Credentials" β "Create Credentials" β "OAuth client ID" 5. Pilih "Web application" 6. Tambahkan Authorized redirect URIs: - http://localhost:3000/api/auth/callback/google - https://domain-anda.com/api/auth/callback/google 7. Salin Client ID dan Client Secret ke .env.local
GitHub OAuth Provider
Langkah-langkah setup GitHub OAuth: 1. Buka https://github.com/settings/developers 2. Klik "New OAuth App" 3. Isi form: - Application name: Nama aplikasi Anda - Homepage URL: http://localhost:3000 - Authorization callback URL: http://localhost:3000/api/auth/callback/github 4. Klik "Register application" 5. Generate Client Secret 6. Salin Client ID dan Client Secret ke .env.local
Menambah Provider Lain
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';
import FacebookProvider from 'next-auth/providers/facebook';
import TwitterProvider from 'next-auth/providers/twitter';
import AppleProvider from 'next-auth/providers/apple';
import DiscordProvider from 'next-auth/providers/discord';
import EmailProvider from 'next-auth/providers/email';
// Di dalam NextAuth config:
providers: [
// OAuth Providers
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
// Scope tambahan untuk mengakses data lebih banyak
// profile(profile) {
// return {
// id: profile.id.toString(),
// name: profile.name || profile.login,
// email: profile.email,
// image: profile.avatar_url,
// };
// },
}),
FacebookProvider({
clientId: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
}),
TwitterProvider({
clientId: process.env.TWITTER_CLIENT_ID,
clientSecret: process.env.TWITTER_CLIENT_SECRET,
version: '2.0',
}),
AppleProvider({
clientId: process.env.APPLE_CLIENT_ID,
clientSecret: process.env.APPLE_CLIENT_SECRET,
}),
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
}),
// Magic Link / Passwordless
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,
// maxAge: 24 * 60 * 60, // Token berlaku 24 jam
}),
],
Login Buttons Component
'use client';
import { signIn } from 'next-auth/react';
function LoginButtons() {
return (
<div className="login-buttons">
<button
onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
className="btn-google"
>
π΅ Masuk dengan Google
</button>
<button
onClick={() => signIn('github', { callbackUrl: '/dashboard' })}
className="btn-github"
>
β« Masuk dengan GitHub
</button>
<button
onClick={() => signIn('facebook', { callbackUrl: '/dashboard' })}
className="btn-facebook"
>
π· Masuk dengan Facebook
</button>
<div className="divider">atau</div>
<a href="/auth/login" className="btn-credentials">
π§ Masuk dengan Email & Password
</a>
</div>
);
}
export default LoginButtons;
4. Session Management
NextAuth.js mendukung dua strategi session: JWT (default) dan Database. Pilihan ini mempengaruhi bagaimana data session disimpan dan diakses.
JWT vs Database Sessions
| Aspek | JWT Strategy | Database Strategy |
|---|---|---|
| Penyimpanan | Di cookie (encrypted) | Di database |
| Database Required? | Tidak | Ya |
| Server-side Logout | Tidak bisa (token valid sampai expired) | Bisa (hapus dari DB) |
| Performa | π’ Lebih cepat (tidak perlu DB query) | π‘ Perlu query DB setiap request |
| Ukuran Token | ~400 bytes (di cookie) | Hanya session token kecil |
| Rekomendasi | App tanpa database adapter | App dengan database, perlu revoke session |
Menggunakan Session di Komponen
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
function Navbar() {
const { data: session, status } = useSession();
// status: 'loading' | 'authenticated' | 'unauthenticated'
return (
<nav className="navbar">
<a href="/" className="logo">BeebaneLabs</a>
<div className="nav-right">
{status === 'loading' && (
<span className="loading-text">Memuat...</span>
)}
{status === 'unauthenticated' && (
<button onClick={() => signIn()}>
Masuk
</button>
)}
{status === 'authenticated' && session && (
<div className="user-menu">
<img
src={session.user.image || '/default-avatar.png'}
alt={session.user.name}
className="avatar"
/>
<span>{session.user.name}</span>
{session.user.role === 'admin' && (
<span className="badge-admin">Admin</span>
)}
<button onClick={() => signOut({ callbackUrl: '/' })}>
Keluar
</button>
</div>
)}
</div>
</nav>
);
}
export default Navbar;
Server-side Session (App Router)
// Server Component β mendapatkan session di server
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
// Redirect jika belum login
if (!session) {
redirect('/auth/login');
}
return (
<div>
<h1>Dashboard</h1>
<p>Selamat datang, {session.user.name}!</p>
<p>Role: {session.user.role}</p>
<img src={session.user.image} alt="Avatar" />
</div>
);
}
// API Route β proteksi API endpoint
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await getServerSession(authOptions);
if (!session) {
return NextResponse.json(
{ error: 'Tidak terautentikasi' },
{ status: 401 }
);
}
// Hanya admin yang boleh akses
if (session.user.role !== 'admin') {
return NextResponse.json(
{ error: 'Akses ditolak' },
{ status: 403 }
);
}
// Lanjutkan proses...
return NextResponse.json({ data: 'Data rahasia admin' });
}
5. Callbacks
Callbacks adalah fungsi yang dijalankan pada berbagai tahap proses autentikasi. Mereka memungkinkan Anda mengkustomisasi perilaku default NextAuth.js.
Empat Jenis Callbacks
callbacks: {
// βββββββββββββββββββββββββββββββββββββββββββββββ
// 1. signIn β kontrol apakah user boleh login
// Dipanggil saat user mencoba sign in
// Return true = izinkan, false/error = tolak
// βββββββββββββββββββββββββββββββββββββββββββββββ
async signIn({ user, account, profile, email, credentials }) {
// user: data user dari provider atau authorize()
// account: info provider (provider, type, accessToken)
// profile: profil dari provider OAuth
// email: info verifikasi email (untuk Email provider)
// credentials: data dari Credentials provider
// Contoh: blacklist email tertentu
const blacklist = ['spam@example.com'];
if (blacklist.includes(user.email)) {
return false; // Tolak login
}
// Contoh: hanya izinkan email verified dari Google
if (account.provider === 'google' && !profile.email_verified) {
return '/auth/error?error=EmailNotVerified';
}
return true; // Izinkan login
},
// βββββββββββββββββββββββββββββββββββββββββββββββ
// 2. redirect β kustomisasi URL setelah sign in/out
// βββββββββββββββββββββββββββββββββββββββββββββββ
async redirect({ url, baseUrl }) {
// url: URL yang diminta (dari callbackUrl parameter)
// baseUrl: URL dasar aplikasi (NEXTAUTH_URL)
// Default: redirect ke halaman yang diminta
if (url.startsWith('/')) return `${baseUrl}${url}`;
// Pastikan hanya redirect ke domain sendiri
if (new URL(url).origin === baseUrl) return url;
// Fallback ke homepage
return baseUrl;
},
// βββββββββββββββββββββββββββββββββββββββββββββββ
// 3. jwt β kustomisasi isi JWT token
// Hanya dipanggil jika strategy: 'jwt'
// βββββββββββββββββββββββββββββββββββββββββββββββ
async jwt({ token, user, account, profile, trigger, isNewUser, session }) {
// token: JWT token saat ini (persisted antar requests)
// user: data user (hanya ada saat sign in pertama)
// account: info provider (hanya ada saat sign in)
// trigger: 'signIn', 'update', atau 'signUp'
// Saat pertama kali sign in, tambahkan data ke token
if (user) {
token.id = user.id;
token.role = user.role || 'user';
token.provider = account?.provider;
}
// Saat session.update() dipanggil dari client
if (trigger === 'update' && session) {
token.name = session.name;
token.image = session.image;
}
return token;
},
// βββββββββββββββββββββββββββββββββββββββββββββββ
// 4. session β kustomisasi data yang dikirim ke client
// Dipanggil setiap kali useSession() atau getServerSession()
// βββββββββββββββββββββββββββββββββββββββββββββββ
async session({ session, token, user }) {
// session: objek session yang akan dikirim ke client
// token: JWT token (jika strategy: 'jwt')
// user: data user dari database (jika strategy: 'database')
// Tambahkan data dari token ke session
if (token) {
session.user.id = token.id;
session.user.role = token.role;
session.user.provider = token.provider;
}
// Bisa juga tambahkan data dari database
// const dbUser = await prisma.user.findUnique({
// where: { id: token.id }
// });
// session.user.subscription = dbUser.subscription;
return session;
},
},
Update Session dari Client
'use client';
import { useSession } from 'next-auth/react';
function EditProfil() {
const { data: session, update } = useSession();
const handleUpdateNama = async (newName) => {
// Update session β trigger jwt callback dengan trigger: 'update'
await update({
...session,
user: {
...session.user,
name: newName,
},
});
// Session akan di-refresh dengan data baru
};
return (
<div>
<p>Nama: {session?.user?.name}</p>
<button onClick={() => handleUpdateNama('Nama Baru')}>
Ubah Nama
</button>
</div>
);
}
6. Database Adapters
Database Adapter memungkinkan NextAuth.js menyimpan data user, akun, session, dan verification token ke database. Ini diperlukan saat menggunakan database session strategy atau ketika Anda ingin menyimpan data user di database sendiri.
Prisma Adapter Setup
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Tabel ini dibuat otomatis oleh Prisma Adapter
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
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)
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
hashedPassword String? // Untuk credentials provider
role String @default("user")
createdAt DateTime @default(now())
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
import { PrismaClient } from '@prisma/client';
// Prevent multiple instances in development
const globalForPrisma = globalThis;
export const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
Setelah membuat schema, jalankan:
npx prisma migrate devβ buat migration dan update databasenpx prisma generateβ generate Prisma Clientnpx prisma studioβ buka visual database editor di browser
7. Credentials Provider
Credentials Provider memungkinkan login dengan email dan password. Ini berguna ketika Anda ingin sistem autentikasi sendiri selain OAuth.
NextAuth.js merekomendasikan OAuth providers karena lebih aman. Credentials Provider tidak mendukung database sessions dan fitur tertentu seperti account linking. Gunakan hanya jika benar-benar diperlukan.
Register & Login Flow
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
function RegisterPage() {
const [formData, setFormData] = useState({
name: '', email: '', password: '', confirmPassword: '',
});
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const router = useRouter();
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
if (formData.password !== formData.confirmPassword) {
setError('Password tidak cocok');
return;
}
if (formData.password.length < 8) {
setError('Password minimal 8 karakter');
return;
}
setLoading(true);
try {
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: formData.name,
email: formData.email,
password: formData.password,
}),
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || 'Gagal mendaftar');
}
// Redirect ke login setelah berhasil
router.push('/auth/login?registered=true');
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div className="auth-page">
<h1>Daftar Akun Baru</h1>
{error && <p className="error">{error}</p>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Nama Lengkap</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
</div>
<div className="form-group">
<label>Email</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required
minLength={8}
/>
</div>
<div className="form-group">
<label>Konfirmasi Password</label>
<input
type="password"
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Mendaftar...' : 'π Daftar'}
</button>
</form>
<p className="auth-link">
Sudah punya akun? <a href="/auth/login">Masuk di sini</a>
</p>
</div>
);
}
export default RegisterPage;
Register API Route
import { NextResponse } from 'next/server';
import bcrypt from 'bcryptjs';
import { prisma } from '@/lib/prisma';
export async function POST(request) {
try {
const { name, email, password } = await request.json();
// Validasi input
if (!name || !email || !password) {
return NextResponse.json(
{ error: 'Semua field wajib diisi' },
{ status: 400 }
);
}
if (password.length < 8) {
return NextResponse.json(
{ error: 'Password minimal 8 karakter' },
{ status: 400 }
);
}
// Cek apakah email sudah terdaftar
const existingUser = await prisma.user.findUnique({
where: { email },
});
if (existingUser) {
return NextResponse.json(
{ error: 'Email sudah terdaftar' },
{ status: 409 }
);
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Buat user baru
const user = await prisma.user.create({
data: {
name,
email,
hashedPassword,
role: 'user',
},
});
return NextResponse.json(
{
message: 'Registrasi berhasil',
user: { id: user.id, name: user.name, email: user.email },
},
{ status: 201 }
);
} catch (error) {
console.error('Register error:', error);
return NextResponse.json(
{ error: 'Terjadi kesalahan server' },
{ status: 500 }
);
}
}
8. Protecting Routes
NextAuth.js mendukung proteksi route menggunakan Next.js Middleware. Ini memungkinkan Anda melindungi halaman dan API routes dari akses yang tidak sah.
Middleware untuk Auth Protection
import { withAuth } from 'next-auth/middleware';
import { NextResponse } from 'next/server';
// Proteksi semua route yang cocok dengan matcher
export default withAuth(
function middleware(req) {
const { token } = req.nextauth;
const { pathname } = req.nextUrl;
// Proteksi route admin β hanya untuk role admin
if (pathname.startsWith('/admin') && token?.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', req.url));
}
// Proteksi route premium β untuk role premium atau admin
if (pathname.startsWith('/premium') &&
!['admin', 'premium'].includes(token?.role)) {
return NextResponse.redirect(new URL('/upgrade', req.url));
}
return NextResponse.next();
},
{
callbacks: {
// Return true = izinkan akses, false = redirect ke signIn
authorized: ({ token }) => {
return !!token; // User harus terautentikasi
},
},
}
);
// Tentukan route mana yang dilindungi
export const config = {
matcher: [
'/dashboard/:path*',
'/admin/:path*',
'/premium/:path*',
'/settings/:path*',
'/api/protected/:path*',
],
};
HOC untuk Client-side Protection
'use client';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
// Higher-Order Component untuk proteksi halaman
export function withAuth(WrappedComponent, requiredRole = null) {
return function ProtectedComponent(props) {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/auth/login');
}
if (status === 'authenticated' && requiredRole) {
if (session.user.role !== requiredRole &&
session.user.role !== 'admin') {
router.push('/unauthorized');
}
}
}, [status, session, router]);
if (status === 'loading') {
return <div className="loading">Memuat...</div>;
}
if (!session) return null;
return <WrappedComponent {...props} session={session} />;
};
}
// Penggunaan:
// const ProtectedDashboard = withAuth(DashboardPage);
// const AdminPage = withAuth(AdminPanel, 'admin');
9. Best Practices & Keamanan
Checklist Keamanan
| Aspek | Best Practice |
|---|---|
| NEXTAUTH_SECRET | Gunakan string acak minimal 32 karakter, simpan di env vars |
| CSRF | NextAuth.js menangani CSRF otomatis β jangan disable |
| HTTPS | Selalu gunakan HTTPS di production |
| Password Hashing | Gunakan bcrypt dengan salt rounds β₯ 12 |
| Session Max Age | Atur maxAge sesuai kebutuhan (default 30 hari) |
| Rate Limiting | Terapkan rate limiting pada endpoint login/register |
| Input Validation | Validasi semua input di server-side |
| Error Messages | Jangan reveal info sensitif di error message |
- Matikan
debug: truedi production - Gunakan
NEXTAUTH_URLyang benar (https://domain-anda.com) - Monitoring: log semua sign-in events untuk audit trail
- Implementasikan rate limiting (misal: 5 attempts per 15 menit)
- Gunakan HTTP-only cookies (default NextAuth.js)
- Pertimbangkan 2FA untuk akun admin
10. Quiz Pemahaman
Uji pemahaman Anda tentang NextAuth.js:
1. Apa perbedaan utama antara JWT dan Database session strategy?
2. Callback apa yang digunakan untuk menambahkan role ke session?
3. Mengapa NEXTAUTH_SECRET penting?
4. Di mana NextAuth API route ditempatkan di Next.js App Router?
5. Fungsi apa yang digunakan untuk mendapatkan session di server (App Router)?