İçeriğe geç
Sedat Demir
Geri dön

Auth.js v5 ile Framework-Agnostic Authentication Kurulumu: Kapsamlı Rehber

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


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/core

Bir 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-orm

2. 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_SECRET değerini production ortamında mutlaka güçlü bir rastgele değerle oluşturun. Terminal'de npx auth secret komutunu ç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:

  1. AUTH_SECRET'i güçlü tutun: En az 32 karakter uzunluğunda rastgele bir değer kullanın
  2. HTTPS zorunlu kılın: Production'da AUTH_URL mutlaka https:// ile başlamalı
  3. CSRF koruması: Auth.js yerleşik CSRF koruması sağlar, devre dışı bırakmayın
  4. Rate limiting ekleyin: Credentials provider kullanıyorsanız brute-force saldırılara karşı rate limiting uygulayın
  5. Session süresini sınırlayın: Hassas uygulamalarda maxAge değerini kısa tutun
  6. Callback URL doğrulaması: redirect callback'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;
  },
}
  1. Credentials provider'da dikkat: OAuth her zaman daha güvenlidir. Credentials kullanıyorsanız şifreleri mutlaka bcrypt veya argon2 ile 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.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.


Share this post on:

Sonraki Yazı
Better Auth: Lucia'nın Deprecated Olmasından Sonra Yeni Authentication Standardı