Yeni·E-Ticaret Pro Paketi Yayında — Entegre Ödeme, Stok ve Sipariş YönetimiBlog·2025'te Küçük İşletmeler İçin Web Tasarım TrendleriKampanya·Mayıs Ayına Özel %20 İndirim — Kartvizit & Başlangıç Paketleriİçgörü·Müşteri Projelerinde Dönüşüm Oranı Ortalama %40 Artış SağlandıBlog·SEO'ya Yeni Başlayanlar İçin Temel Rehber — Ücretsiz İndirHaber·Ankara'da Yeni Çözüm Ortaklıkları ile Hizmet Ağı GenişliyorGüncelleme·Tüm Projeler İçin Ücretsiz SSL, CDN ve Hız Optimizasyonu DahilYeni·Çözümler Sayfası Açıldı — Sektöre Özel Web ÇözümleriKampanya·Ücretsiz Web Sitesi Değerlendirmesi — Bugün Başvurİçgörü·Ortalama Proje Teslim Süresi: 14 Gün — GarantiliYeni·E-Ticaret Pro Paketi Yayında — Entegre Ödeme, Stok ve Sipariş YönetimiBlog·2025'te Küçük İşletmeler İçin Web Tasarım TrendleriKampanya·Mayıs Ayına Özel %20 İndirim — Kartvizit & Başlangıç Paketleriİçgörü·Müşteri Projelerinde Dönüşüm Oranı Ortalama %40 Artış SağlandıBlog·SEO'ya Yeni Başlayanlar İçin Temel Rehber — Ücretsiz İndirHaber·Ankara'da Yeni Çözüm Ortaklıkları ile Hizmet Ağı GenişliyorGüncelleme·Tüm Projeler İçin Ücretsiz SSL, CDN ve Hız Optimizasyonu DahilYeni·Çözümler Sayfası Açıldı — Sektöre Özel Web ÇözümleriKampanya·Ücretsiz Web Sitesi Değerlendirmesi — Bugün Başvurİçgörü·Ortalama Proje Teslim Süresi: 14 Gün — Garantili
ilkkod
Next.js Entegrasyonları

Next.js ile Better Auth: NextAuth'a Gerek Yok — Modern Kimlik Doğrulama Rehberi

Better Auth v1.5 ile Next.js 16'da email/şifre, magic link ve OAuth kurulumu. Drizzle adapter, proxy.ts entegrasyonu, rol tabanlı erişim ve 2FA plugin — sıfırdan production'a tam rehber.

İlker
23 Mart 2026
18 dk
Next.js ile Better Auth: NextAuth'a Gerek Yok — Modern Kimlik Doğrulama Rehberi

Önemli Not: Bu yazıdaki teknik bilgiler yazım tarihi itibarıyla geçerlidir. Kullanılan kütüphaneler, API'ler ve servisler zaman içinde değişebilir. Ücretlendirme, yasal düzenleme ve vergi konularında ilgili resmi kaynakları ve uzmanları referans alınız. Bu içerik bilgilendirme amaçlı olup herhangi bir finansal veya hukuki tavsiye niteliği taşımamaktadır.

Next.js ekosisteminde kimlik doğrulama söz konusu olduğunda ilk akla gelen isim hâlâ NextAuth — artık Auth.js adıyla bilinen bu kütüphane, uzun yıllar boyunca fiilî standart olarak kabul gördü. Ancak 2025 sonunda bu resim ciddi ölçüde değişti. Better Auth, TypeScript-first tasarımı, zengin plugin ekosistemi ve ücretsiz self-hosted yapısıyla modern Next.js projelerinin yeni kimlik doğrulama tercihi hâline geldi.

Bu rehberde Better Auth v1.5 (Mart 2026) ile Next.js 16 App Router'da sıfırdan kimlik doğrulama sistemi kuruyoruz. Email/şifre girişi, magic link, OAuth provider'ları, Drizzle ORM adapter, proxy.ts ile route koruması ve rol tabanlı erişim — hepsini adım adım ele alıyoruz.

Bu projenin (ilkkod.com) hem admin paneli hem müşteri portalı Better Auth üzerinde çalışıyor. Yani teorik değil, gerçek production deneyimiyle yazılmış bir rehber.


2026'da Next.js Auth Ekosistemi

Bir kütüphane seçmeden önce seçenekleri karşılaştırmak gerekiyor:

Better AuthNextAuth v5 (Auth.js)ClerkLucia
FiyatÜcretsiz, self-hostedÜcretsiz, self-hostedÜcretsiz tier (sınırlı kullanıcı)Ücretsiz, self-hosted
TypeScript✅ Tam, type-safe✅ İyi
Drizzle adapter✅ Resmi❌ Yok (Prisma odaklı)N/AManuel
Plugin sayısı50+SınırlıOrtaMinimal
Vendor lock-in✅ Var
Durum✅ Aktif (v1.5.5)✅ Aktif✅ Aktif⚠️ Bakım modu

Lucia Mart 2025'te bakım moduna alındı — yeni projelerde önerilmiyor. Clerk, kullanıcı başına ücretlendirmesi ve vendor lock-in riski nedeniyle kendi veri tabanını kontrol etmek isteyen projeler için dezavantajlı. NextAuth v5, hâlâ iyi bir seçenek; ancak Drizzle adapter eksikliği ve database session yönetimi karmaşıklığı, Drizzle kullanan projelerde süreci zorlaştırıyor.

Better Auth ise TypeScript-first mimarisi, resmi Drizzle adapter'ı ve zengin plugin sistemiyle bu tabloda öne çıkıyor.


Kurulum

bun add better-auth

Better Auth, Drizzle ORM ile doğrudan çalışır. Drizzle'ı henüz kurmadıysanız Drizzle ORM rehberimize göz atın.


Veritabanı Şeması

Better Auth, gerekli tabloları Drizzle şemanıza otomatik ekleyebilir. Bunun için CLI aracını kullanın:

bunx better-auth@latest generate

Bu komut, seçtiğiniz plugin'lere göre user, session, account, verification tablolarının Drizzle şema kodunu çıkarır. Çıktıyı mevcut şema dosyanıza ekleyin ya da ayrı bir dosyada tutun:

// lib/db/schema/auth.ts import { pgTable, text, boolean, timestamp } from "drizzle-orm/pg-core" export const user = pgTable("user", { id: text("id").primaryKey(), name: text("name").notNull(), email: text("email").notNull().unique(), emailVerified: boolean("email_verified") .$defaultFn(() => false) .notNull(), image: text("image"), createdAt: timestamp("created_at") .$defaultFn(() => new Date()) .notNull(), updatedAt: timestamp("updated_at") .$defaultFn(() => new Date()) .notNull(), // Rol tabanlı erişim için role: text("role").$type<"admin" | "user">().default("user").notNull(), }) export const session = pgTable("session", { id: text("id").primaryKey(), expiresAt: timestamp("expires_at").notNull(), token: text("token").notNull().unique(), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), ipAddress: text("ip_address"), userAgent: text("user_agent"), userId: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), }) export const account = pgTable("account", { id: text("id").primaryKey(), accountId: text("account_id").notNull(), providerId: text("provider_id").notNull(), userId: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), accessToken: text("access_token"), refreshToken: text("refresh_token"), idToken: text("id_token"), accessTokenExpiresAt: timestamp("access_token_expires_at"), refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), scope: text("scope"), password: text("password"), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), }) export const verification = pgTable("verification", { id: text("id").primaryKey(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").$defaultFn(() => new Date()), updatedAt: timestamp("updated_at").$defaultFn(() => new Date()), })

Şemayı veritabanına yansıtın:

bunx drizzle-kit push

auth.ts Konfigürasyon Dosyası

Tüm Better Auth konfigürasyonu tek bir dosyada toplanır:

// lib/auth.ts import { betterAuth } from "better-auth" import { drizzleAdapter } from "better-auth/adapters/drizzle" import { magicLink } from "better-auth/plugins" import { db } from "@/lib/db" import * as schema from "@/lib/db/schema/auth" import { resend } from "@/lib/email/resend" // Resend örneği export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema: { user: schema.user, session: schema.session, account: schema.account, verification: schema.verification, }, }), // Email/şifre kimlik doğrulama emailAndPassword: { enabled: true, requireEmailVerification: false, // Admin paneli için false tutabilirsiniz }, // Magic link plugin (müşteri portalı için) plugins: [ magicLink({ sendMagicLink: async ({ email, token, url }) => { await resend.emails.send({ from: "ilkkod <noreply@ilkkod.com>", to: email, subject: "Giriş Bağlantınız", html: ` <p>Müşteri portalına giriş yapmak için aşağıdaki bağlantıya tıklayın:</p> <a href="${url}" style=" display: inline-block; padding: 12px 24px; background: #FFB800; color: #000; text-decoration: none; border-radius: 6px; font-weight: 600; ">Giriş Yap</a> <p>Bu bağlantı 5 dakika geçerlidir.</p> `, }) }, expiresIn: 300, // 5 dakika (saniye) allowedAttempts: 1, disableSignUp: false, // İlk girişte otomatik kayıt }), ], // OAuth provider'ları (opsiyonel) socialProviders: { 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!, }, }, // Session konfigürasyonu session: { expiresIn: 60 * 60 * 24 * 30, // 30 gün (saniye) updateAge: 60 * 60 * 24, // Her 24 saatte bir yenile cookieCache: { enabled: true, maxAge: 5 * 60, // 5 dakika cookie cache }, }, // Güvenilir kaynaklara izin ver trustedOrigins: [ process.env.BETTER_AUTH_URL!, ], }) export type Session = typeof auth.$Infer.Session export type User = typeof auth.$Infer.Session.user

Zorunlu Ortam Değişkenleri

# .env.local BETTER_AUTH_SECRET=en-az-32-karakter-uzun-rastgele-bir-deger BETTER_AUTH_URL=http://localhost:3000 # Production'da gerçek URL # OAuth (opsiyonel) GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET=

BETTER_AUTH_SECRET minimum 32 karakter olmalıdır. Güvenli bir değer üretmek için:

openssl rand -base64 32

Next.js Route Handler Kurulumu

Better Auth, tüm kimlik doğrulama isteklerini tek bir catch-all Route Handler üzerinden yönetir:

// app/api/auth/[...all]/route.ts import { auth } from "@/lib/auth" import { toNextJsHandler } from "better-auth/next-js" export const { POST, GET } = toNextJsHandler(auth)

Bu kadar. Bu tek dosya /api/auth/sign-in, /api/auth/sign-out, /api/auth/magic-link, /api/auth/callback/* gibi tüm endpoint'leri otomatik olarak oluşturur.


Client Tarafı: Auth Client Kurulumu

// lib/auth-client.ts import { createAuthClient } from "better-auth/react" import { magicLinkClient } from "better-auth/client/plugins" export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_APP_URL, plugins: [magicLinkClient()], }) // Kullanışlı export'lar export const { signIn, signOut, signUp, useSession, } = authClient

Kimlik Doğrulama Yöntemleri

1. Email/Şifre (Admin Paneli)

// components/admin/login-form.tsx "use client" import { useState } from "react" import { useRouter } from "next/navigation" import { authClient } from "@/lib/auth-client" export function AdminLoginForm() { const router = useRouter() const [error, setError] = useState<string | null>(null) const [loading, setLoading] = useState(false) async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault() setLoading(true) setError(null) const formData = new FormData(e.currentTarget) const { data, error } = await authClient.signIn.email({ email: formData.get("email") as string, password: formData.get("password") as string, }) if (error) { setError("E-posta veya şifre hatalı.") setLoading(false) return } router.push("/yonetim-paneli") } return ( <form onSubmit={handleSubmit}> <input name="email" type="email" required placeholder="E-posta" /> <input name="password" type="password" required placeholder="Şifre" /> {error && <p>{error}</p>} <button type="submit" disabled={loading}> {loading ? "Giriş yapılıyor..." : "Giriş Yap"} </button> </form> ) }

Magic link, şifresiz giriş için idealdir. Müşteri e-posta adresini girer, gelen magic link'e tıklar ve otomatik olarak giriş yapar. Bu projenin müşteri portalı bu yöntemi kullanıyor.

// components/portal/magic-link-form.tsx "use client" import { useState } from "react" import { authClient } from "@/lib/auth-client" export function MagicLinkForm() { const [sent, setSent] = useState(false) const [loading, setLoading] = useState(false) async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault() setLoading(true) const formData = new FormData(e.currentTarget) const { error } = await authClient.signIn.magicLink({ email: formData.get("email") as string, callbackURL: "/musteri-portali", }) if (!error) setSent(true) setLoading(false) } if (sent) { return ( <p> E-postanızı kontrol edin. Giriş bağlantısı birkaç saniye içinde ulaşacak. </p> ) } return ( <form onSubmit={handleSubmit}> <input name="email" type="email" required placeholder="E-posta adresiniz" /> <button type="submit" disabled={loading}> {loading ? "Gönderiliyor..." : "Giriş Bağlantısı Gönder"} </button> </form> ) }

Magic link callback rotası için ayrıca bir page.tsx oluşturmanıza gerek yok — Better Auth bunu /api/auth handler üzerinden otomatik yönetir.

3. OAuth ile Giriş (Google, GitHub)

// OAuth butonu await authClient.signIn.social({ provider: "google", callbackURL: "/dashboard", })

Server Component'larda Session Okuma

Server Component veya Route Handler içinde mevcut kullanıcıyı şu şekilde alırsınız:

// app/(admin)/yonetim-paneli/page.tsx import { auth } from "@/lib/auth" import { headers } from "next/headers" import { redirect } from "next/navigation" export default async function AdminPage() { const session = await auth.api.getSession({ headers: await headers(), }) if (!session) { redirect("/giris") } // session.user.id, session.user.email, session.user.role erişilebilir return <div>Hoş geldiniz, {session.user.name}</div> }

Next.js 16 notu: headers() çağrısı artık asyncawait headers() yazmalısınız. Senkron erişim Next.js 16'da tamamen kaldırıldı.


Client Component'larda Session Okuma

// components/portal/user-menu.tsx "use client" import { useSession } from "@/lib/auth-client" export function UserMenu() { const { data: session, isPending } = useSession() if (isPending) return <div>Yükleniyor...</div> if (!session) return null return ( <div> <span>{session.user.email}</span> </div> ) }

Next.js 16: proxy.ts ile Route Koruması

Next.js 16'nın en kritik değişikliğinden biri: middleware.ts yerini proxy.ts'e bıraktı. Better Auth'ın resmi belgelerinde proxy.ts için özel bir kısım bulunmasa da entegrasyon oldukça düzgün çalışıyor:

// proxy.ts (proje kökünde — middleware.ts değil!) import { auth } from "@/lib/auth" import { NextResponse } from "next/server" import type { NextRequest } from "next/server" // Korunan yollar const ADMIN_PATHS = ["/yonetim-paneli"] const PORTAL_PATHS = ["/musteri-portali"] const PUBLIC_PATHS = ["/giris", "/musteri-portali/giris", "/api/auth"] export async function proxy(request: NextRequest) { const pathname = request.nextUrl.pathname // Public path'lere dokunma if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) { return NextResponse.next() } // Admin panel koruması if (ADMIN_PATHS.some((p) => pathname.startsWith(p))) { const session = await auth.api.getSession({ headers: request.headers, }) if (!session) { return NextResponse.redirect(new URL("/giris", request.url)) } // Sadece admin rolüne izin ver if (session.user.role !== "admin") { return NextResponse.redirect(new URL("/", request.url)) } } // Müşteri portalı koruması if (PORTAL_PATHS.some((p) => pathname.startsWith(p))) { const session = await auth.api.getSession({ headers: request.headers, }) if (!session) { return NextResponse.redirect( new URL("/musteri-portali/giris", request.url), ) } } return NextResponse.next() }

Önemli: proxy.ts'deki fonksiyon adı middleware değil, proxy olmalıdır. Bu Next.js 16'nın zorunlu kıldığı bir değişikliktir.


Rol Tabanlı Erişim

Better Auth'da kullanıcı rolleri için birkaç yaklaşım var. Bu projede en basit yol: user tablosuna role kolonu eklemek.

Şema değişikliği zaten yukarıda gösterildi. Giriş sırasında rol bilgisi session'a otomatik taşınır.

Server Action ile Rol Kontrolü

// lib/auth/require-admin.ts import { auth } from "@/lib/auth" import { headers } from "next/headers" import { redirect } from "next/navigation" export async function requireAdmin() { const session = await auth.api.getSession({ headers: await headers(), }) if (!session || session.user.role !== "admin") { redirect("/giris") } return session }

Kullanımı:

// app/(admin)/yonetim-paneli/kullanicilar/page.tsx import { requireAdmin } from "@/lib/auth/require-admin" export default async function UsersPage() { const session = await requireAdmin() // Yetkisizse redirect atar return <div>Kullanıcı yönetimi</div> }

Better Auth Plugin Sistemi

Better Auth'un 50'yi aşkın plugin'i, kimlik doğrulamayı ihtiyaç duyduğunuz kadar genişletmenizi sağlar.

İki Faktörlü Doğrulama (2FA)

// lib/auth.ts — plugin ekle import { twoFactor } from "better-auth/plugins" export const auth = betterAuth({ // ... diğer konfigürasyon plugins: [ magicLink({ /* ... */ }), twoFactor({ issuer: "ilkkod", otpOptions: { digits: 6, period: 30, }, }), ], })

Client tarafında:

import { twoFactorClient } from "better-auth/client/plugins" export const authClient = createAuthClient({ plugins: [ magicLinkClient(), twoFactorClient({ twoFactorPage: "/2fa", }), ], }) // 2FA etkinleştirme (TOTP QR kodu üretir) const { data } = await authClient.twoFactor.enable({ password: "mevcut-sifre", }) // data.totpURI → QR kodu için kullanın // Giriş sırasında 2FA kodu doğrulama await authClient.twoFactor.verifyTotp({ code: "123456" })

Diğer Önemli Plugin'ler

  • Email OTP: Magic link yerine 6 haneli kod gönderme
  • Passkey: WebAuthn / parmak izi / yüz tanıma desteği
  • Organization: Çok kiracılı (multi-tenant) yapılar için takım yönetimi
  • Admin: Kullanıcı listeleme, yasaklama, impersonation
  • Rate Limiting: Kaba kuvvet saldırılarına karşı built-in koruma

Session Süresi ve Token Yönetimi

Better Auth, cookie tabanlı session yönetimi kullanır. JWT değil, veritabanı session'ı — bu daha güvenli, ancak her istekte DB sorgusu gerektirir.

session: { expiresIn: 60 * 60 * 24 * 30, // 30 gün updateAge: 60 * 60 * 24, // 24 saatte bir session yenile cookieCache: { enabled: true, maxAge: 5 * 60, // 5 dakika önbellek — DB yükünü azaltır }, },

cookieCache etkinleştirildiğinde, session bilgisi HTTP-only cookie'de 5 dakika önbelleğe alınır. Bu süre içinde gelen istekler veritabanına gitmez — yüksek trafikli uygulamalarda önemli bir performans kazanımı.

Session'ı sunucu tarafından geçersiz kılmak için:

await auth.api.revokeSession({ token: sessionToken }) await auth.api.revokeUserSessions({ userId: "..." }) // Tüm oturumları kapat

Güvenlik Notları

Production'da Better Auth, BETTER_AUTH_URL HTTPS ile başlıyorsa session cookie'lerini otomatik olarak Secure flag'iyle işaretler. Geliştirme ortamında HTTP sorunsuz çalışır.

CSRF Koruması

Better Auth, tüm state-modifying endpoint'leri (sign-in, sign-out, vb.) için built-in CSRF koruması sunar. Ekstra bir şey yapmanıza gerek yok.

Parolaların Saklanması

Email/şifre kimlik doğrulamasında parolalar, argon2id algoritmasıyla hash'lenerek veritabanına kaydedilir. Ham parolalar hiçbir zaman saklanmaz.


Production Kontrol Listesi

Uygulamanızı canlıya almadan önce:

  • BETTER_AUTH_SECRET production'da güçlü ve eşsiz bir değer
  • BETTER_AUTH_URL doğru domain (örn. https://www.example.com)
  • Magic link URL'lerinin gönderilen domain ile eşleştiğini doğrulayın
  • OAuth callback URL'lerini provider konsollarında kaydedin
  • Email gönderimi için alan adı doğrulaması tamamlandı (SPF/DKIM)
  • proxy.ts ile korunan rotalar test edildi
  • Rol tabanlı erişim kontrolleri uçtan uca test edildi
  • Session timeout ve revocation senaryoları test edildi
  • 2FA akışı eksiksiz test edildi (varsa)

Sıkça Sorulan Sorular

Better Auth ücretsiz mi?

Evet, Better Auth tamamen ücretsiz ve açık kaynaklı. Tüm özellikler ve 50'yi aşkın plugin MIT lisansıyla sunuluyor. Kullanıcı başına ücret yok, self-hosted çalışıyor. Ticari destek planları için resmi web sitesini inceleyin.

NextAuth'dan Better Auth'a geçiş nasıl yapılır?

Temel adımlar şunlardır: mevcut veritabanı tablolarınızı Better Auth şemasına taşıma, /api/auth/[...nextauth] handler'ı /api/auth/[...all] ile değiştirme, ve client tarafında useSession import path'ini güncelleme. Resmi migration kılavuzu için Better Auth belgelerine bakın.

Evet. Bu projenin yaptığı tam olarak bu: admin paneli email/şifre, müşteri portalı magic link kullanıyor. Her iki yöntem aynı user tablosunu paylaşır; kullanıcı kaydı hangi yöntemle olursa olsun aynı tabloya düşer.

Better Auth, Edge Runtime'da çalışır mı?

Drizzle adapter ile birlikte Edge Runtime desteklenmez — Node.js runtime gerektirir. Bu nedenle proxy.ts Node.js üzerinde çalışır (Next.js 16'nın varsayılan davranışıdır). Edge ihtiyacınız varsa Better Auth'un JWT stratejisi veya harici session store değerlendirilebilir.

Çok kiracılı (multi-tenant) yapılar için Better Auth uygun mu?

Evet, Better Auth'un Organization plugin'i tam multi-tenant desteği sunar: takım/organizasyon oluşturma, üye davetleri, rol yönetimi (owner, admin, member). Kurumsal uygulamalar için yeterince güçlü bir yapı sunar.

Rate limiting nasıl çalışıyor?

Better Auth, built-in rate limiting içerir. Başarısız giriş denemelerini otomatik sınırlar. Ek konfigürasyon ihtiyaçları için özel Rate Limiting plugin'i de mevcuttur.


Sonuç

Better Auth, 2026 itibarıyla Next.js projelerinde kimlik doğrulama için olgun ve güvenilir bir seçenek. TypeScript-first yaklaşımı, resmi Drizzle adapter'ı ve Next.js 16'nın proxy.ts özelliğiyle sorunsuz entegrasyonu, Drizzle kullanan projelerde onu özellikle cazip kılıyor.

Bu projede hem admin paneli hem müşteri portalı Better Auth üzerinde production'da çalışıyor. Magic link ile şifresiz müşteri girişi, email/şifre ile admin erişimi — Better Auth her iki senaryoyu aynı codebase içinde temiz biçimde yönetiyor.

Modern Next.js stack'inin diğer parçalarını merak ediyorsanız Drizzle ORM rehberimize veya PayTR ödeme entegrasyonu rehberimize göz atabilirsiniz.

Paylaş: