Skip to content

Ders 08: Authentication ve Authorization

  • Authentication vs Authorization farkı
  • Session tabanlı authentication
  • JWT token yönetimi
  • Route koruma (protected routes)
  • Role-based access control (RBAC)
  • OAuth entegrasyonu
  • Çıkış yapma ve session yönetimi

KavramTürkçeAçıklamaÖrnek
AuthenticationKimlik Doğrulama”Kimsin?”Kullanıcı girişi
AuthorizationYetkilendirme”Ne yapabilirsin?”Admin yetkisi
// Authentication: Kullanıcı giriş yapmış mı?
if (!session.userId) {
throw redirect({ to: '/login' })
}
// Authorization: Kullanıcı bu işlemi yapabilir mi?
if (user.rol !== 'admin') {
throw new Error('Yetkisiz erişim!')
}
1. Kullanıcı login sayfasına gider
2. Email/şifre girer ve form gönderir
3. Server credentials'ı kontrol eder
4. Başarılıysa session/token oluşturur
5. Tarayıcıya session cookie gönderilir
6. Sonraki isteklerde cookie otomatik gönderilir
7. Server session'ı doğrular ve kullanıcıya erişim verir

Session tabanlı auth, kullanıcı bilgilerinin sunucuda saklandığı yöntemdir.

src/lib/session.ts
import { useSession } from '@tanstack/react-start/server'
type SessionData = {
userId?: string
email?: string
ad?: string
rol?: 'admin' | 'user' | 'moderator'
}
export function getSession() {
return useSession<SessionData>({
name: 'app-session',
password: process.env.SESSION_SECRET!, // En az 32 karakter
cookie: {
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
httpOnly: true, // XSS koruması
maxAge: 60 * 60 * 24 * 7, // 1 hafta
path: '/',
},
})
}
src/lib/auth.ts
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { getSession } from './session'
// Mock kullanıcı veritabanı
const KULLANICILAR = [
{ id: '1', email: 'admin@example.com', sifre: 'admin123', ad: 'Admin', rol: 'admin' as const },
{ id: '2', email: 'user@example.com', sifre: 'user123', ad: 'Kullanıcı', rol: 'user' as const },
]
// Login function
export const login = createServerFn({ method: 'POST' })
.inputValidator(
z.object({
email: z.string().email('Geçerli bir email girin'),
sifre: z.string().min(6, 'Şifre en az 6 karakter'),
})
)
.handler(async ({ data }) => {
// Kullanıcıyı bul
const kullanici = KULLANICILAR.find(
(k) => k.email === data.email && k.sifre === data.sifre
)
if (!kullanici) {
throw new Error('Geçersiz email veya şifre')
}
// Session oluştur
const session = await getSession()
await session.update({
userId: kullanici.id,
email: kullanici.email,
ad: kullanici.ad,
rol: kullanici.rol,
})
return {
basari: true,
kullanici: {
id: kullanici.id,
ad: kullanici.ad,
email: kullanici.email,
rol: kullanici.rol,
},
}
})
// src/lib/auth.ts (devam)
export const logout = createServerFn({ method: 'POST' })
.handler(async () => {
const session = await getSession()
await session.clear()
return { basari: true }
})
// src/lib/auth.ts (devam)
export const getCurrentUser = createServerFn({ method: 'GET' })
.handler(async () => {
const session = await getSession()
if (!session.data.userId) {
return null
}
return {
id: session.data.userId,
email: session.data.email,
ad: session.data.ad,
rol: session.data.rol,
}
})

src/routes/login.tsx
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router'
import { useServerFn } from '@tanstack/react-start'
import { login } from '../lib/auth'
import React from 'react'
export const Route = createFileRoute('/login')({
component: LoginPage,
// Zaten giriş yapmışsa dashboard'a yönlendir
loader: async () => {
// Mevcut kullanıcıyı kontrol et
// (Bu basit örnek, gerçek uygulamada getCurrentUser kullanın)
return {}
},
})
function LoginPage() {
const navigate = useNavigate()
const loginFn = useServerFn(login)
const [email, setEmail] = React.useState('')
const [sifre, setSifre] = React.useState('')
const [hata, setHata] = React.useState('')
const [yukleniyor, setYukleniyor] = React.useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setHata('')
setYukleniyor(true)
try {
await loginFn({ data: { email, sifre } })
// Başarılı giriş - dashboard'a yönlendir
navigate({ to: '/dashboard', replace: true })
} catch (error: any) {
setHata(error.message || 'Giriş başarısız')
} finally {
setYukleniyor(false)
}
}
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh', backgroundColor: '#f3f4f6' }}>
<div style={{ backgroundColor: 'white', padding: '2rem', borderRadius: '8px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)', width: '100%', maxWidth: '400px' }}>
<h1 style={{ fontSize: '1.5rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>
Giriş Yap
</h1>
{hata && (
<div style={{ backgroundColor: '#fee2e2', color: '#dc2626', padding: '0.75rem', borderRadius: '4px', marginBottom: '1rem' }}>
{hata}
</div>
)}
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div>
<label style={{ display: 'block', marginBottom: '0.25rem', fontWeight: '500' }}>
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="ornek@email.com"
required
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '1rem',
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.25rem', fontWeight: '500' }}>
Şifre
</label>
<input
type="password"
value={sifre}
onChange={(e) => setSifre(e.target.value)}
placeholder="******"
required
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '1rem',
}}
/>
</div>
<button
type="submit"
disabled={yukleniyor}
style={{
padding: '0.75rem',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '4px',
fontSize: '1rem',
fontWeight: '500',
cursor: yukleniyor ? 'not-allowed' : 'pointer',
opacity: yukleniyor ? 0.5 : 1,
}}
>
{yukleniyor ? 'Giriş yapılıyor...' : 'Giriş Yap'}
</button>
</form>
<p style={{ marginTop: '1rem', textAlign: 'center', fontSize: '0.875rem', color: '#6b7280' }}>
Test hesapları: admin@example.com / admin123
</p>
</div>
</div>
)
}

🛡️ Protected Routes (Korumalı Route’lar)

Section titled “🛡️ Protected Routes (Korumalı Route’lar)”

Protected route’lar, sadece giriş yapmış kullanıcıların erişebildiği sayfalardır.

src/middleware/auth.ts
import { createMiddleware } from '@tanstack/react-start'
import { getSession } from '../lib/session'
export const authMiddleware = createMiddleware()
.server(async ({ next }) => {
const session = await getSession()
if (!session.data.userId) {
// Giriş yapılmamış - login sayfasına yönlendir
throw redirect({
to: '/login',
search: {
redirect: window.location.pathname,
},
})
}
// Giriş yapılmış - context'e kullanıcı bilgisini ekle
return next({
context: {
user: session.data,
},
})
})
src/routes/dashboard/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { authMiddleware } from '../../middleware/auth'
export const Route = createFileRoute('/dashboard/')({
// Auth middleware'i kullan
middleware: [authMiddleware],
component: DashboardHome,
})
function DashboardHome() {
// Context'ten kullanıcı bilgisini al
// Not: Bu context tip tanımına ihtiyaç duyar
return (
<div>
<h1>Dashboard</h1>
<p>Hoş geldiniz!</p>
</div>
)
}
src/routes/profil/index.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { getCurrentUser } from '../../lib/auth'
export const Route = createFileRoute('/profil/')({
component: ProfilPage,
loader: async () => {
const user = await getCurrentUser()
if (!user) {
throw redirect({
to: '/login',
search: { redirect: '/profil' },
})
}
return { user }
},
})
function ProfilPage() {
const { user } = Route.useLoaderData()
return (
<div>
<h1>Profilim</h1>
<p>Ad: {user.ad}</p>
<p>Email: {user.email}</p>
<p>Rol: {user.rol}</p>
</div>
)
}

RBAC, kullanıcı rolüne göre yetki kontrolü yapar.

src/lib/auth.ts
import { createMiddleware } from '@tanstack/react-start'
export const requireRole = (...roller: ('admin' | 'moderator')[]) => {
return createMiddleware()
.server(async ({ next }) => {
const session = await getSession()
if (!session.data.userId) {
throw redirect({ to: '/login' })
}
const kullaniciRol = session.data.rol
if (!roller.includes(kullaniciRol)) {
throw new Error('Bu sayfaya erişim yetkiniz yok')
}
return next({
context: {
user: session.data,
},
})
})
}
src/routes/admin/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { requireRole } from '../../lib/auth'
// Sadece adminler erişebilir
export const Route = createFileRoute('/admin/')({
middleware: [requireRole('admin')],
component: AdminPage,
})
function AdminPage() {
return (
<div>
<h1>Admin Paneli</h1>
<p>Bu sayfaya sadece adminler erişebilir.</p>
</div>
)
}
src/routes/moderator/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { requireRole } from '../../lib/auth'
// Hem admin hem moderator erişebilir
export const Route = createFileRoute('/moderator/')({
middleware: [requireRole('admin', 'moderator')],
component: ModeratorPage,
})
function ModeratorPage() {
return (
<div>
<h1>Moderator Paneli</h1>
<p>Bu sayfaya admin ve moderatorler erişebilir.</p>
</div>
)
}
// Bazen bileşen içinde rol kontrolü gerekebilir
function AdminButton({ user }: { user: { rol: string } }) {
// Sadece admin görür
if (user.rol !== 'admin') {
return null
}
return (
<button style={{ backgroundColor: '#dc2626', color: 'white' }}>
Sil
</button>
)
}

JWT (JSON Web Token), kullanıcı bilgilerinin token içinde saklandığı stateless auth yöntemidir.

src/lib/jwt.ts
import { sign, verify } from 'jsonwebtoken'
const JWT_SECRET = process.env.JWT_SECRET!
type TokenPayload = {
userId: string
email: string
rol: string
}
export function createToken(payload: TokenPayload): string {
return sign(payload, JWT_SECRET, {
expiresIn: '7d',
})
}
export function verifyToken(token: string): TokenPayload | null {
try {
return verify(token, JWT_SECRET) as TokenPayload
} catch {
return null
}
}
src/lib/auth-jwt.ts
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { createToken } from './jwt'
export const loginWithJWT = createServerFn({ method: 'POST' })
.inputValidator(
z.object({
email: z.string().email(),
sifre: z.string(),
})
)
.handler(async ({ data }) => {
const kullanici = await authenticateUser(data.email, data.sifre)
if (!kullanici) {
throw new Error('Geçersiz bilgiler')
}
// JWT token oluştur
const token = createToken({
userId: kullanici.id,
email: kullanici.email,
rol: kullanici.rol,
})
// Token'ı cookie olarak set et
// (Bu örnek için basit, gerçek uygulamada httpOnly cookie kullanın)
return {
token,
kullanici: {
id: kullanici.id,
ad: kullanici.ad,
email: kullanici.email,
rol: kullanici.rol,
},
}
})

OAuth, Google, GitHub gibi üçüncü parti servislerle giriş yapmayı sağlar.

src/lib/oauth.ts
import { createServerFn } from '@tanstack/react-start'
export const getGoogleAuthUrl = createServerFn({ method: 'GET' })
.handler(async () => {
const redirectUri = `${process.env.APP_URL}/auth/google/callback`
const params = new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID!,
redirect_uri: redirectUri,
response_type: 'code',
scope: 'profile email',
})
return {
authUrl: `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`,
}
})
export const googleCallback = createServerFn({ method: 'GET' })
.inputValidator(
z.object({
code: z.string(),
})
)
.handler(async ({ data }) => {
// 1. Code'u access token ile değiştir
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
code: data.code,
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
redirect_uri: `${process.env.APP_URL}/auth/google/callback`,
grant_type: 'authorization_code',
}),
}).then((r) => r.json())
// 2. Kullanıcı bilgilerini al
const userInfo = await fetch(
'https://www.googleapis.com/oauth2/v2/userinfo',
{
headers: {
Authorization: `Bearer ${tokenResponse.access_token}`,
},
}
).then((r) => r.json())
// 3. Kullanıcıyı oluştur veya giriş yap
const user = await findOrCreateUser(userInfo)
// 4. Session oluştur
const session = await getSession()
await session.update({
userId: user.id,
email: user.email,
ad: user.ad,
rol: user.rol,
})
return { basari: true }
})
src/routes/auth/google/callback.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { googleCallback } from '../../../lib/oauth'
export const Route = createFileRoute('/auth/google/callback')({
loader: async ({ search }) => {
const { code } = search
if (!code) {
throw redirect({ to: '/login' })
}
await googleCallback({ data: { code } })
throw redirect({ to: '/dashboard' })
},
})

Hadi öğrendiklerimizle tam bir auth sistemi oluşturalım!

src/components/AuthContext.tsx
import { createContext, useContext, ReactNode } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { getCurrentUser, login, logout } from '../lib/auth'
type User = {
id: string
ad: string
email: string
rol: 'admin' | 'user'
}
type AuthContextType = {
user: User | null | undefined
isLoading: boolean
loginMutation: any
logoutMutation: any
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: ReactNode }) {
const queryClient = useQueryClient()
// Mevcut kullanıcıyı getir
const { data: user, isLoading } = useQuery({
queryKey: ['currentUser'],
queryFn: getCurrentUser,
retry: false,
staleTime: 1000 * 60 * 5, // 5 dakika
})
// Login mutation
const loginMutation = useMutation({
mutationFn: login,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['currentUser'] })
},
})
// Logout mutation
const logoutMutation = useMutation({
mutationFn: logout,
onSuccess: () => {
queryClient.clear()
window.location.href = '/login'
},
})
return (
<AuthContext.Provider
value={{
user,
isLoading,
loginMutation,
logoutMutation,
}}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
src/components/Header.tsx
import { Link } from '@tanstack/react-router'
import { useAuth } from './AuthContext'
export function Header() {
const { user, logoutMutation } = useAuth()
return (
<header style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '1rem 2rem',
backgroundColor: '#3b82f6',
color: 'white',
}}>
<Link to="/" style={{ color: 'white', textDecoration: 'none', fontSize: '1.25rem', fontWeight: 'bold' }}>
Uygulamam
</Link>
<nav style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Link to="/" style={{ color: 'white' }}>Ana Sayfa</Link>
<Link to="/dashboard" style={{ color: 'white' }}>Dashboard</Link>
{user ? (
<>
<span>Merhaba, {user.ad}!</span>
{user.rol === 'admin' && (
<Link to="/admin" style={{ color: 'white' }}>Admin</Link>
)}
<button
onClick={() => logoutMutation.mutate()}
style={{
padding: '0.5rem 1rem',
backgroundColor: 'white',
color: '#3b82f6',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Çıkış
</button>
</>
) : (
<Link
to="/login"
style={{
padding: '0.5rem 1rem',
backgroundColor: 'white',
color: '#3b82f6',
borderRadius: '4px',
textDecoration: 'none',
}}
>
Giriş Yap
</Link>
)}
</nav>
</header>
)
}

Bu derste öğrendiklerimiz:

KonuAçıklama
AuthenticationKimlik doğrulama (“Kimsin?”)
AuthorizationYetkilendirme (“Ne yapabilirsin?“)
useSession()Session yönetimi
Session-based authSunucuda session saklama
JWTToken-based auth
Protected routesGiriş gerektiren sayfalar
RBACRole-based access control
OAuthÜçüncü parti auth (Google, GitHub)
StratejiAvantajDezavantaj
SessionGüvenli, sunucu kontrolüSunucu hafıza kullanımı
JWTStateless, ölçeklenebilirToken iptali zor
OAuthKolay kullanımÜçüncü parti bağımlılığı

Kullanıcı kayıt sayfası oluşturun:

src/routes/register.tsx
// Email, şifre, şifre tekrar alanları
// Kayıt başarılıysa otomatik giriş

Şifremi unuttum akışı oluşturun:

// 1. Email iste
// 2. Reset linki gönder (mock)
// 3. Yeni şifre formu
// 4. Şifre güncelle

Admin route’larını tam korumaya alın:

// /admin/* tüm route'lar için admin kontrolü
// Middleware ile merkezileştirin

🚀 Sonraki Ders: Form Yönetimi ve Validasyon

Section titled “🚀 Sonraki Ders: Form Yönetimi ve Validasyon”

Bir sonraki derste şunları öğreneceksiniz:

  • 📝 Form state yönetimi
  • ✅ Zod ile validasyon
  • 🎯 Client vs server validasyon
  • 🔄 Form submission handling
  • 💾 Form verisi saklama
  • 🎨 Form UI bileşenleri

  1. Session vs JWT hangisi daha iyi?
  2. Token nasıl iptal edilir (JWT)?
  3. OAuth güvenliği nasıl sağlanır?

Bir sonraki derste görüşmek üzere! 👋