Auth.js v5 ile Framework-Agnostic Authentication Kurulumu: Kapsamlı Rehber
Authentication (kimlik doğrulama), her modern web uygulamasının temel taşıdır. Yıllardır Next.js ekosisteminin vazgeçilmezi olan NextAuth.js, v5 sürümüyle birlikte köklü bir dönüşüm geçirdi ve Auth.js adını aldı. Bu dönüşümün en büyük yeniliği: artık tek bir framework'e bağımlı değilsiniz. SvelteKit, Express, Remix, Qwik ve hatta vanilla Node.js projelerinizde aynı authentication altyapısını kullanabilirsiniz.
Bu yazıda Auth.js v5'in mimarisini, framework-agnostic kurulumunu ve gerçek dünya senaryolarında nasıl kullanılacağını detaylıca ele alacağız.
Auth.js v5 Nedir ve Neden Önemlidir?
Auth.js v5, kimlik doğrulama süreçlerini standartlaştırmak için tasarlanmış açık kaynaklı bir kütüphanedir. Önceki sürümlerden farklı olarak çekirdek (core) katmanı framework'lerden tamamen bağımsız hale getirildi.
v4'ten v5'e Geçişte Temel Değişiklikler
- Framework-agnostic core:
@auth/corepaketi herhangi bir framework olmadan çalışır - Edge Runtime desteği: Cloudflare Workers, Vercel Edge Functions gibi ortamlarla uyumlu
- Web Standard API'leri:
RequestveResponsenesneleri üzerine inşa edilmiştir - Yeni konfigürasyon yapısı: Daha modüler ve tip güvenli yapı
- Geliştirilmiş TypeScript desteği: End-to-end tip güvenliği
Mimari Yapıyı Anlamak
Auth.js v5'in mimarisi üç katmandan oluşur:
┌─────────────────────────────────┐
│ Framework Adaptörleri │
│ (@auth/nextjs, @auth/sveltekit, │
│ @auth/express, vb.) │
├─────────────────────────────────┤
│ @auth/core │
│ (Framework-agnostic çekirdek) │
├─────────────────────────────────┤
│ Database Adaptörleri │
│ (@auth/prisma-adapter, │
│ @auth/drizzle-adapter, vb.) │
└─────────────────────────────────┘Bu katmanlı yapı sayesinde çekirdek authentication mantığını bir kez yazıp, herhangi bir framework'e adapte edebilirsiniz.
Kurulum: Adım Adım
1. Temel Paketlerin Yüklenmesi
Hangi framework'ü kullanırsanız kullanın, temel paketler şunlardır:
# Next.js için
npm install next-auth@5 @auth/core
# SvelteKit için
npm install @auth/sveltekit @auth/core
# Express için
npm install @auth/express @auth/core
# Genel amaçlı (vanilla/custom)
npm install @auth/coreBir database adaptörü de eklemeniz gerekecek:
# Prisma kullanıyorsanız
npm install @auth/prisma-adapter @prisma/client prisma
# Drizzle kullanıyorsanız
npm install @auth/drizzle-adapter drizzle-orm2. Ortam Değişkenlerini Ayarlama
Projenizin kök dizininde bir .env.local dosyası oluşturun:
# Zorunlu: Auth.js secret key (openssl rand -base64 33 ile üretilebilir)
AUTH_SECRET="super-gizli-random-key-buraya-yazin"
# Google OAuth
AUTH_GOOGLE_ID="google-client-id"
AUTH_GOOGLE_SECRET="google-client-secret"
# GitHub OAuth
AUTH_GITHUB_ID="github-client-id"
AUTH_GITHUB_SECRET="github-client-secret"
# Uygulama URL'i
AUTH_URL="http://localhost:3000"
# Veritabanı
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"Güvenlik Notu:
AUTH_SECRETdeğerini production ortamında mutlaka güçlü bir rastgele değerle oluşturun. Terminal'denpx auth secretkomutunu çalıştırarak otomatik üretebilirsiniz.
Framework-Agnostic Core Konfigürasyonu
İşin güzelliği burada başlıyor. Önce framework'den bağımsız bir auth.config.ts dosyası oluşturuyoruz:
// auth.config.ts
import Google from "@auth/core/providers/google";
import GitHub from "@auth/core/providers/github";
import Credentials from "@auth/core/providers/credentials";
import type { NextAuthConfig } from "next-auth"; // veya @auth/core tiplerini kullanabilirsiniz
export const authConfig = {
providers: [
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
}),
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
}),
Credentials({
name: "Email & Şifre",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "ornek@mail.com",
},
password: {
label: "Şifre",
type: "password",
},
},
async authorize(credentials) {
// Burada kendi doğrulama mantığınızı yazın
const { email, password } = credentials as {
email: string;
password: string;
};
// Veritabanından kullanıcı sorgulama
const user = await getUserFromDatabase(email);
if (!user) return null;
// Şifre doğrulama (bcrypt gibi bir kütüphane kullanın)
const isValid = await verifyPassword(password, user.hashedPassword);
if (!isValid) return null;
return {
id: user.id,
name: user.name,
email: user.email,
image: user.image,
};
},
}),
],
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith("/dashboard");
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // Login sayfasına yönlendir
}
return true;
},
async jwt({ token, user, account }) {
// İlk giriş sırasında kullanıcı bilgilerini token'a ekle
if (user) {
token.id = user.id;
token.role = (user as any).role || "user";
}
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
// Session'a özel alanlar ekle
if (session.user) {
session.user.id = token.id as string;
(session.user as any).role = token.role;
}
return session;
},
},
pages: {
signIn: "/auth/login",
signOut: "/auth/logout",
error: "/auth/error",
newUser: "/auth/register",
},
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 gün
},
trustHost: true,
} satisfies NextAuthConfig;Bu konfigürasyon dosyası tamamen framework-agnostic'tir. Artık bunu farklı framework adaptörlerine bağlayabiliriz.
Next.js ile Entegrasyon (App Router)
Next.js App Router kullanıyorsanız, kurulum şu şekilde:
// auth.ts (proje kökünde)
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
import { authConfig } from "./auth.config";
const prisma = new PrismaClient();
export const {
handlers,
auth,
signIn,
signOut,
} = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
});API Route Handler
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;Middleware ile Route Koruması
// middleware.ts
import { auth } from "./auth";
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
const protectedRoutes = ["/dashboard", "/profile", "/settings"];
const isProtected = protectedRoutes.some((route) =>
nextUrl.pathname.startsWith(route)
);
if (isProtected && !isLoggedIn) {
const loginUrl = new URL("/auth/login", nextUrl.origin);
loginUrl.searchParams.set("callbackUrl", nextUrl.pathname);
return Response.redirect(loginUrl);
}
});
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};Server Component'te Session Kullanımı
// 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("/auth/login");
}
return (
<div className="p-8">
<h1 className="text-2xl font-bold">
Hoş geldin, {session.user.name}!
</h1>
<p className="text-gray-600">Email: {session.user.email}</p>
<img
src={session.user.image || "/default-avatar.png"}
alt="Profil"
className="w-16 h-16 rounded-full mt-4"
/>
</div>
);
}Client Component'te Session Kullanımı
// components/providers/session-provider.tsx
"use client";
import { SessionProvider } from "next-auth/react";
export function AuthProvider({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}// app/layout.tsx
import { AuthProvider } from "@/components/providers/session-provider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="tr">
<body>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
);
}// components/user-menu.tsx
"use client";
import { useSession, signIn, signOut } from "next-auth/react";
export function UserMenu() {
const { data: session, status } = useSession();
if (status === "loading") {
return <div className="animate-pulse h-10 w-10 bg-gray-200 rounded-full" />;
}
if (!session) {
return (
<button
onClick={() => signIn()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"
>
Giriş Yap
</button>
);
}
return (
<div className="flex items-center gap-3">
<span className="text-sm font-medium">{session.user?.name}</span>
<button
onClick={() => signOut()}
className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition"
>
Çıkış Yap
</button>
</div>
);
}Express.js ile Entegrasyon
Auth.js v5'in framework-agnostic doğasını en iyi gösteren örneklerden biri Express entegrasyonudur:
// server.ts
import express from "express";
import { ExpressAuth } from "@auth/express";
import { authConfig } from "./auth.config";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
const app = express();
const prisma = new PrismaClient();
// Auth.js middleware'ini Express'e bağla
app.use(
"/api/auth/*",
ExpressAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
})
);
// Korumalı route örneği
app.get("/api/protected", async (req, res) => {
const session = (req as any).auth;
if (!session) {
return res.status(401).json({ error: "Yetkisiz erişim" });
}
res.json({
message: `Merhaba ${session.user.name}!`,
user: session.user,
});
});
app.listen(3000, () => {
console.log("Sunucu 3000 portunda çalışıyor");
});@auth/core ile Vanilla Kullanım
Hiçbir framework adaptörü kullanmadan, doğrudan @auth/core ile çalışmak da mümkündür:
// vanilla-auth.ts
import { Auth } from "@auth/core";
import Google from "@auth/core/providers/google";
const request = new Request("https://example.com/api/auth/session", {
method: "GET",
headers: {
cookie: "session-token=abc123",
},
});
const response = await Auth(request, {
secret: process.env.AUTH_SECRET,
providers: [
Google({
clientId: process.env.AUTH_GOOGLE_ID!,
clientSecret: process.env.AUTH_GOOGLE_SECRET!,
}),
],
trustHost: true,
});
const session = await response.json();
console.log("Aktif session:", session);Bu yaklaşım Cloudflare Workers, Deno, Bun veya herhangi bir Web Standard API destekleyen runtime'da çalışır.
Prisma ile Veritabanı Şeması
Auth.js'in database session yönetimi için gereken Prisma şeması:
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
role String @default("user")
accounts Account[]
sessions Session[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
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])
}TypeScript ile Tip Genişletme
Auth.js'in varsayılan tiplerini projenize özel alanlarla genişletebilirsiniz:
// types/next-auth.d.ts
import { DefaultSession, DefaultUser } from "next-auth";
import { DefaultJWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
user: {
id: string;
role: string;
} & DefaultSession["user"];
}
interface User extends DefaultUser {
role: string;
}
}
declare module "next-auth/jwt" {
interface JWT extends DefaultJWT {
id: string;
role: string;
accessToken?: string;
}
}Güvenlik İpuçları ve Best Practices
Authentication sisteminizi production'a taşırken dikkat etmeniz gereken kritik noktalar:
- AUTH_SECRET'i güçlü tutun: En az 32 karakter uzunluğunda rastgele bir değer kullanın
- HTTPS zorunlu kılın: Production'da
AUTH_URLmutlakahttps://ile başlamalı - CSRF koruması: Auth.js yerleşik CSRF koruması sağlar, devre dışı bırakmayın
- Rate limiting ekleyin: Credentials provider kullanıyorsanız brute-force saldırılara karşı rate limiting uygulayın
- Session süresini sınırlayın: Hassas uygulamalarda
maxAgedeğerini kısa tutun - Callback URL doğrulaması:
redirectcallback'inde sadece kendi domain'inize yönlendirmelere izin verin:
callbacks: {
async redirect({ url, baseUrl }) {
// Sadece aynı origin'e yönlendirmeye izin ver
if (url.startsWith("/")) return `${baseUrl}${url}`;
if (new URL(url).origin === baseUrl) return url;
return baseUrl;
},
}- Credentials provider'da dikkat: OAuth her zaman daha güvenlidir. Credentials kullanıyorsanız şifreleri mutlaka
bcryptveyaargon2ile hashleyin.
Sık Karşılaşılan Hatalar ve Çözümleri
| Hata | Sebep | Çözüm |
|---|---|---|
MISSING_SECRET |
AUTH_SECRET tanımlanmamış | .env.local dosyasına ekleyin |
OAUTH_CALLBACK_ERROR |
Callback URL uyuşmazlığı | OAuth provider ayarlarında doğru URL'yi ekleyin |
JWT_SESSION_ERROR |
Token süresi dolmuş | maxAge değerini kontrol edin |
ADAPTER_ERROR |
Veritabanı bağlantı sorunu | DATABASE_URL ve şema migrasyonlarını kontrol edin |
Sonuç
Auth.js v5, authentication dünyasında gerçek bir paradigma değişimi sunuyor. Framework-agnostic core sayesinde authentication mantığınızı bir kez yazıp, Next.js'ten Express'e, SvelteKit'ten Cloudflare Workers'a kadar her yerde kullanabilirsiniz.
Bu yazıda ele aldığımız temel noktalar:
- @auth/core katmanının framework'lerden bağımsız çalıştığı
- Ortak bir
auth.config.tsile tüm provider'ların merkezi yönetimi - Next.js App Router, Express ve vanilla ortamlarda entegrasyon örnekleri
- Prisma adapter ile veritabanı oturum yönetimi
- TypeScript tip genişletme ile güvenli geliştirme deneyimi
- Production güvenlik best practice'leri
Auth.js v5'in resmi dokümantasyonunu authjs.dev adresinden takip etmenizi ve güncellemeleri kaçırmamanızı tavsiye ederim. Authentication asla "bir kez yap unut" değildir — güvenlik yamalarını ve yeni özellikleri düzenli olarak takip edin.
Bir sonraki adım olarak role-based access control (RBAC), multi-factor authentication (MFA) ve magic link implementasyonlarını Auth.js v5 üzerinde incelemenizi öneririm.