Auth.js v5 Nedir ve Neden Tercih Edilmeli?
Auth.js v5 (eski adıyla NextAuth.js), Next.js uygulamalarına kimlik doğrulama eklemek için en popüler açık kaynaklı kütüphanelerden biri. Belki de birincisi demek daha doğru olur. Sürüm 5 ile birlikte kütüphane sıfırdan yeniden yazıldı ve App Router ile tam uyumlu hale getirildi. OAuth sağlayıcıları (Google, GitHub, Discord vb.), e-posta/şifre ile giriş, oturum yönetimi ve yetkilendirme işlemlerini tek bir auth() fonksiyonu üzerinden yönetebiliyorsunuz.
Peki v5 ile tam olarak ne değişti? Hadi bir bakalım:
- Tek bir NextAuth() çağrısı: Artık ayrı bir
authOptionsnesnesi taşımaya gerek yok.auth,signIn,signOutvehandlersfonksiyonları tek bir yerden dışa aktarılıyor. - Server Components desteği:
auth()fonksiyonunu Server Components içinde doğrudan kullanabilirsiniz. Cidden, bu kadar basit. - Edge ve Node.js uyumu: JWT oturumları Edge Runtime'da, veritabanı oturumları Node.js ortamında sorunsuz çalışıyor.
- TypeScript öncelikli: Tüm tipler yerleşik olarak geliyor, özel alanlar için tip genişletme desteği de mevcut.
- Otomatik ortam değişkenleri:
AUTH_önekli değişkenler otomatik olarak algılanıyor — ekstra yapılandırma yok.
Proje Kurulumu ve Temel Yapılandırma
Gereksinimler
Bu rehberde şu teknolojileri kullanacağız:
- Next.js 16.x (App Router)
- Auth.js v5 (next-auth@beta)
- Prisma ORM (veritabanı adaptörü için)
- TypeScript
Kurulum
Öncelikle yeni bir Next.js projesi oluşturun ya da mevcut projenize Auth.js paketlerini ekleyin:
npx create-next-app@latest my-auth-app --typescript --app
cd my-auth-app
npm install next-auth@beta @auth/prisma-adapter
npm install prisma @prisma/client bcryptjs
npm install -D @types/bcryptjs
Ortam Değişkenlerini Ayarlama
Proje kök dizininde bir .env.local dosyası oluşturun. Auth.js v5'te tüm değişkenler AUTH_ önekiyle başlıyor; eski NEXTAUTH_ öneki artık kullanılmıyor:
# .env.local
AUTH_SECRET=your-random-secret-key-here
AUTH_URL=http://localhost:3000
# Google OAuth
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
# GitHub OAuth
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
# Veritabanı
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
AUTH_SECRET değerini oluşturmak için terminalde şu komutu çalıştırmanız yeterli:
npx auth secret
Auth.js v5 Yapılandırma Dosyaları
auth.config.ts — Edge Uyumlu Yapılandırma
Auth.js v5'te yapılandırma iki dosyaya bölünüyor. Bu ilk bakışta gereksiz gibi görünebilir ama arkasında önemli bir neden var: Edge Runtime'da çalışması gereken kodla, Node.js'e bağımlı kodun ayrıştırılması. İlk dosya, veritabanı adaptörü gerektirmeyen ve Edge Runtime'da çalışabilen ayarları içerir:
// auth.config.ts
import type { NextAuthConfig } 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 { z } from "zod";
import bcrypt from "bcryptjs";
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export default {
providers: [
Google,
GitHub,
Credentials({
name: "E-posta ve Şifre",
credentials: {
email: { label: "E-posta", type: "email" },
password: { label: "Şifre", type: "password" },
},
async authorize(credentials) {
const parsed = loginSchema.safeParse(credentials);
if (!parsed.success) return null;
// Burada veritabanından kullanıcıyı çekin
// Örnek: const user = await getUserByEmail(parsed.data.email);
// if (!user || !user.password) return null;
// const isValid = await bcrypt.compare(parsed.data.password, user.password);
// if (!isValid) return null;
// return { id: user.id, name: user.name, email: user.email, role: user.role };
return null; // Gerçek uygulamada yukarıdaki kodu aktif edin
},
}),
],
pages: {
signIn: "/giris",
error: "/giris",
},
} satisfies NextAuthConfig;
auth.ts — Ana Yapılandırma Dosyası
İkinci dosya işin kalbi. Prisma adaptörünü ve callback fonksiyonlarını burada tanımlıyorsunuz. Uygulamanın geri kalanında kullanacağınız tüm fonksiyonlar bu dosyadan dışa aktarılıyor:
// auth.ts
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
import authConfig from "./auth.config";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: "jwt" },
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (token) {
session.user.id = token.sub!;
session.user.role = token.role as string;
}
return session;
},
async authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith("/dashboard");
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // Giriş sayfasına yönlendir
}
return true;
},
},
...authConfig,
});
Route Handler Oluşturma
Auth.js'in giriş/çıkış ve callback isteklerini işleyebilmesi için bir route handler gerekli. Bu kısım kısa ama olmazsa olmaz:
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
Prisma Şeması ve Veritabanı Kurulumu
Auth.js v5, kullanıcı ve oturum verilerini saklamak için bir veritabanı adaptörü kullanıyor. Prisma şemanıza role alanını da eklemeniz önemli — RBAC kısmında buna ihtiyacımız olacak:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
password String?
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? @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 VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
Şemayı oluşturduktan sonra veritabanını migrate edin:
npx prisma generate
npx prisma db push
Prisma İstemcisi
Prisma istemcisini tekil örnek (singleton) olarak oluşturmak şart. Bunu yapmazsanız, geliştirme ortamında hot reload her tetiklendiğinde yeni bir bağlantı açılır ve kısa sürede bağlantı havuzunu tüketirsiniz:
// lib/prisma.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
TypeScript Tip Genişletmeleri
Auth.js v5, session.user nesnesine özel alanlar eklediğinizde (bizim durumumuzda role), TypeScript'in bu alanları tanıması için tip genişletmesi yapmanız gerekiyor. Bu adımı atlamak, derleyici hatalarıyla uğraşmanıza neden olur:
// types/next-auth.d.ts
import { DefaultSession } from "next-auth";
declare module "next-auth" {
interface User {
role?: string;
}
interface Session {
user: {
id: string;
role: string;
} & DefaultSession["user"];
}
}
declare module "next-auth/jwt" {
interface JWT {
role?: string;
}
}
Next.js 16'da proxy.ts ile Rota Koruması
Next.js 16 ile birlikte middleware.ts dosyası proxy.ts olarak yeniden adlandırıldı. Ama bu sadece bir isim değişikliği değil. Proxy artık varsayılan olarak Node.js runtime'ında çalışıyor (Edge Runtime yerine). Bu sayede Prisma gibi ORM'ler doğrudan proxy içinde kullanılabiliyor — eskiden bunu yapmak için bin dereden su getirmek gerekiyordu.
// proxy.ts
import { auth } from "./auth";
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
const protectedRoutes = ["/dashboard", "/profil", "/ayarlar"];
const authRoutes = ["/giris", "/kayit"];
const isProtected = protectedRoutes.some((route) =>
nextUrl.pathname.startsWith(route)
);
const isAuthRoute = authRoutes.some((route) =>
nextUrl.pathname.startsWith(route)
);
// Korunan sayfalara erişim kontrolü
if (isProtected && !isLoggedIn) {
return Response.redirect(new URL("/giris", nextUrl));
}
// Giriş yapmış kullanıcıları auth sayfalarından yönlendir
if (isAuthRoute && isLoggedIn) {
return Response.redirect(new URL("/dashboard", nextUrl));
}
});
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
Önemli not: Next.js 15 veya daha eski bir sürüm kullanıyorsanız dosya adını middleware.ts olarak bırakın ve dışa aktarılan fonksiyon adını middleware yapın. Next.js 16+'da ise proxy.ts ve default export kullanılıyor.
Google OAuth Sağlayıcısını Yapılandırma
Google Cloud Console Ayarları
Google OAuth entegrasyonu için şu adımları izleyin:
- Google Cloud Console'a gidin ve yeni bir proje oluşturun.
- APIs & Services > Credentials bölümüne gidin.
- OAuth 2.0 Client ID oluşturun (Web application tipinde).
- Authorized redirect URIs alanına şunu ekleyin:
http://localhost:3000/api/auth/callback/google - Üretim ortamı için alan adınızı ekleyin:
https://siteadi.com/api/auth/callback/google - Oluşturulan
Client IDveClient Secretdeğerlerini.env.localdosyanıza yapıştırın.
Auth.js v5'te AUTH_GOOGLE_ID ve AUTH_GOOGLE_SECRET ortam değişkenleri otomatik olarak algılanıyor, yani provider yapılandırmasında ayrıca belirtmenize gerek yok. Güzel bir detay.
Giriş ve Çıkış İşlemleri
Server Action ile Giriş
Auth.js v5, signIn ve signOut fonksiyonlarını Server Actions olarak kullanmanızı sağlıyor. Bu yaklaşımın güzel tarafı, istemci tarafında herhangi bir API çağrısı yönetmenize gerek kalmaması:
// app/actions/auth.ts
"use server";
import { signIn, signOut } from "@/auth";
export async function googleSignIn() {
await signIn("google", { redirectTo: "/dashboard" });
}
export async function githubSignIn() {
await signIn("github", { redirectTo: "/dashboard" });
}
export async function credentialsSignIn(formData: FormData) {
await signIn("credentials", {
email: formData.get("email") as string,
password: formData.get("password") as string,
redirectTo: "/dashboard",
});
}
export async function handleSignOut() {
await signOut({ redirectTo: "/" });
}
Giriş Sayfası Bileşeni
Şimdi gelelim bu Server Actions'ları kullanan giriş sayfasına. Hem OAuth butonları hem de e-posta/şifre formu tek bir sayfada:
// app/giris/page.tsx
import { googleSignIn, githubSignIn, credentialsSignIn } from "@/app/actions/auth";
export default function LoginPage() {
return (
<div className="max-w-md mx-auto mt-20 p-6">
<h1 className="text-2xl font-bold mb-6">Giriş Yap</h1>
{/* OAuth Butonları */}
<form action={googleSignIn} className="mb-3">
<button
type="submit"
className="w-full bg-white border border-gray-300 rounded-lg px-4 py-2 hover:bg-gray-50"
>
Google ile Giriş Yap
</button>
</form>
<form action={githubSignIn} className="mb-6">
<button
type="submit"
className="w-full bg-gray-900 text-white rounded-lg px-4 py-2 hover:bg-gray-800"
>
GitHub ile Giriş Yap
</button>
</form>
<hr className="my-6" />
{/* E-posta/Şifre Formu */}
<form action={credentialsSignIn}>
<div className="mb-4">
<label htmlFor="email" className="block text-sm font-medium mb-1">
E-posta
</label>
<input
id="email"
name="email"
type="email"
required
className="w-full border rounded-lg px-3 py-2"
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-sm font-medium mb-1">
Şifre
</label>
<input
id="password"
name="password"
type="password"
required
className="w-full border rounded-lg px-3 py-2"
/>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white rounded-lg px-4 py-2 hover:bg-blue-700"
>
Giriş Yap
</button>
</form>
</div>
);
}
Server Components'ta Oturum Kontrolü
Auth.js v5'in en büyük avantajlarından biri, auth() fonksiyonunu Server Components içinde doğrudan çağırabilmeniz. Artık getServerSession çağırıp yanına authOptions geçmeye gerek yok. Açıkçası bu değişiklik tek başına bile v5'e geçmeye değer:
// app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect("/giris");
}
return (
<div>
<h1>Hoş geldiniz, {session.user.name}</h1>
<p>E-posta: {session.user.email}</p>
<p>Rol: {session.user.role}</p>
</div>
);
}
Client Components'ta Oturum Kullanımı
Client Components'ta oturum bilgisine erişmek için SessionProvider ve useSession hook'u kullanılıyor. Önce bir provider wrapper oluşturun, sonra bunu layout'a sarın:
// 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="tr">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Rol Tabanlı Erişim Kontrolü (RBAC)
Şimdi işin biraz daha heyecanlı kısmına geldik. Auth.js v5 ile kullanıcı rollerini veritabanında saklayarak farklı yetkilendirme seviyeleri uygulayabilirsiniz. Admin paneli, editör arayüzü ve standart kullanıcı sayfalarını birbirinden ayırmak istiyorsanız bu yapı tam size göre.
Rol Kontrol Yardımcı Fonksiyonu
// lib/auth-guard.ts
import { auth } from "@/auth";
type Role = "admin" | "editor" | "user";
export async function requireRole(requiredRoles: Role | Role[]) {
const session = await auth();
if (!session?.user) {
throw new Error("Oturum açmanız gerekiyor.");
}
const roles = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles];
const userRole = session.user.role as Role;
if (!roles.includes(userRole)) {
throw new Error("Bu sayfaya erişim yetkiniz yok.");
}
return session;
}
export async function getCurrentUser() {
const session = await auth();
return session?.user ?? null;
}
Admin Sayfası Örneği
Kullanımı son derece basit. Sadece sayfanın başında rol kontrolü yapın:
// app/admin/page.tsx
import { requireRole } from "@/lib/auth-guard";
export default async function AdminPage() {
const session = await requireRole("admin");
return (
<div>
<h1>Admin Paneli</h1>
<p>Hoş geldiniz, {session.user.name} (Admin)</p>
</div>
);
}
Server Action'da Rol Kontrolü
Sadece sayfa seviyesinde kontrol yetmez, Server Action'lar içinde de aynı kontrolü yapmayı ihmal etmeyin. Yoksa doğrudan API çağrısıyla korumayı atlatmak mümkün olur:
// app/actions/admin.ts
"use server";
import { requireRole } from "@/lib/auth-guard";
import { prisma } from "@/lib/prisma";
export async function deleteUser(userId: string) {
await requireRole("admin");
await prisma.user.delete({
where: { id: userId },
});
return { success: true };
}
JWT ve Veritabanı Oturum Stratejileri
Auth.js v5 iki farklı oturum stratejisi sunuyor. Hangisini seçeceğiniz uygulamanızın mimarisine ve dağıtım ortamına bağlı. İkisinin de avantajları ve dezavantajları var, o yüzden körü körüne birini seçmek yerine ihtiyacınıza göre karar vermenizi öneririm.
JWT Stratejisi
- Oturum verileri şifreli bir çerezde (cookie) saklanıyor.
- Her istekte veritabanı sorgusu gerekmez — daha hızlı.
- Serverless ve Edge ortamları için ideal.
- Ama bir dezavantajı var: oturumu anında iptal etmek mümkün değil. Çerezin süresi dolana kadar geçerli kalıyor.
Veritabanı Stratejisi
- Oturum verileri veritabanında saklanıyor.
- Her istekte veritabanı sorgusu yapılıyor (performans maliyeti var).
- Oturum anında iptal edilebilir — örneğin şifre değişikliğinde tüm oturumları sonlandırabilirsiniz.
- Daha yüksek güvenlik gerektiren uygulamalar için uygun.
// JWT stratejisi (varsayılan)
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: "jwt" },
...authConfig,
});
// Veritabanı stratejisi
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: "database" },
...authConfig,
});
Çoğu uygulama için JWT stratejisi yeterli ve performanslı. Ama finans veya sağlık gibi hassas verilerin olduğu projelerde veritabanı stratejisini düşünmenizi tavsiye ederim.
Güvenlik En İyi Uygulamaları
Kimlik doğrulama sisteminizi güvende tutmak için bu maddelere mutlaka dikkat edin. Güvenlik konusunda paranoyak olmak, yeterince paranoyak olmamaktan her zaman daha iyidir:
- Üç katmanlı güvenlik: Proxy (rota koruması) + Server Actions (işlem doğrulaması) + Veritabanı seviyesi güvenlik (RLS). Hiçbir katman tek başına yeterli değil.
- CSRF koruması: Auth.js v5,
signInvesignOutişlemleri için yerleşik CSRF token koruması sağlıyor. Bu konuda ekstra bir şey yapmanıza gerek yok. - Şifre hashleme: Şifreleri veritabanına kaydetmeden önce mutlaka
bcryptile hashleyin. Düz metin şifre saklamak 2026'da affedilmez bir hata. - AUTH_SECRET güvenliği:
AUTH_SECRETdeğeri en az 32 karakter uzunluğunda, rastgele oluşturulmuş bir dize olmalı. Bu değeri asla kaynak koduna yazmayın. - Rate limiting: Giriş denemelerini sınırlandırarak brute-force saldırılarını önleyin.
- CVE-2025-29927 dersi: Yalnızca middleware'e güvenmeyin. Bu güvenlik açığı, middleware tabanlı korumanın tek başına yetersiz olduğunu net bir şekilde kanıtladı. Veri erişim noktalarında (Server Actions, Route Handlers) da mutlaka doğrulama yapın.
Üretim Ortamına Dağıtım
Her şey yerel ortamda çalışıyor, harika. Ama üretim ortamına taşırken dikkat etmeniz gereken birkaç kritik nokta var:
- AUTH_SECRET: Üretim ortamında bu değişkenin tanımlı olması zorunlu. Tanımlı değilse Auth.js dümdüz hata fırlatır.
- AUTH_URL: Vercel'de otomatik olarak algılanıyor. Kendi sunucunuzda barındırıyorsanız tam URL'i belirtmeniz gerekiyor.
- Callback URL'leri: Google ve GitHub OAuth ayarlarında üretim alan adınızı eklemeyi unutmayın. Bu adım sıkça atlanan ama hata ayıklamada çok zaman kaybettiren bir detay.
- HTTPS zorunluluğu: Üretim ortamında oturum çerezleri
Securebayrağıyla gönderilir; bu nedenle HTTPS şart. - Veritabanı bağlantı havuzu: Serverless ortamlarda Prisma'nın bağlantı havuzu (connection pooling) özelliğini yapılandırın, yoksa bağlantı limitlerine takılabilirsiniz.
Sıkça Sorulan Sorular
Auth.js v5 ile NextAuth v4 arasındaki fark nedir?
Auth.js v5, NextAuth v4'ün tamamen yeniden yazılmış hali. En büyük fark, tüm fonksiyonların (auth, signIn, signOut, handlers) tek bir NextAuth() çağrısından dışa aktarılması. Artık getServerSession(authOptions) yerine sadece auth() kullanılıyor. Ayrıca NEXTAUTH_ öneki AUTH_ olarak değişti ve OAuth 1.0 desteği kaldırıldı.
Next.js 16'da middleware.ts yerine proxy.ts mı kullanmalıyım?
Evet. Next.js 16 ile birlikte middleware.ts dosyası proxy.ts olarak yeniden adlandırıldı ve artık Node.js runtime'ında çalışıyor. Mevcut mantığınız aynı kalır; yalnızca dosya adını ve dışa aktarılan fonksiyon adını güncellemeniz yeterli. Next.js bu geçiş için otomatik bir codemod da sunuyor.
JWT ve veritabanı oturum stratejisi arasında nasıl seçim yapmalıyım?
Serverless veya Edge ortamlarında dağıtım yapıyorsanız JWT stratejisini tercih edin — her istekte veritabanı sorgusu yapılmaz ve daha hızlıdır. Oturumları anında iptal etme ihtiyacınız varsa (mesela şifre sıfırlama sonrası) veritabanı stratejisine yönelin. Deneyimlerime göre çoğu proje için JWT yeterli oluyor.
Auth.js v5 ile birden fazla OAuth sağlayıcısı nasıl eklenir?
providers dizisine istediğiniz kadar sağlayıcı ekleyebilirsiniz. Auth.js v5, AUTH_ önekli ortam değişkenlerini otomatik algılıyor. Google için AUTH_GOOGLE_ID ve AUTH_GOOGLE_SECRET, GitHub için AUTH_GITHUB_ID ve AUTH_GITHUB_SECRET tanımlamanız yeterli. Her sağlayıcı için ayrı callback URL'i yapılandırmayı unutmayın.
Credentials provider ile OAuth provider birlikte kullanılabilir mi?
Evet, Auth.js v5'te ikisini aynı anda kullanabilirsiniz. Ancak dikkat etmeniz gereken bir şey var: Credentials provider kullanıyorsanız oturum stratejisi olarak jwt kullanmanız gerekiyor; veritabanı oturum stratejisi Credentials provider ile çalışmaz. E-posta/şifre ile giriş yapan kullanıcılar ile OAuth ile giriş yapanları aynı hesaba bağlamak (account linking) istiyorsanız ek yapılandırma gerekebilir.