Ders 08: Authentication ve Authorization
🎯 Bu Derste Neleri Öğreneceksiniz?
Section titled “🎯 Bu Derste Neleri Öğreneceksiniz?”- 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
📚 Temel Kavramlar
Section titled “📚 Temel Kavramlar”Authentication vs Authorization
Section titled “Authentication vs Authorization”| Kavram | Türkçe | Açıklama | Örnek |
|---|---|---|---|
| Authentication | Kimlik Doğrulama | ”Kimsin?” | Kullanıcı girişi |
| Authorization | Yetkilendirme | ”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!')}Auth Akışı
Section titled “Auth Akışı”1. Kullanıcı login sayfasına gider2. Email/şifre girer ve form gönderir3. Server credentials'ı kontrol eder4. Başarılıysa session/token oluşturur5. Tarayıcıya session cookie gönderilir6. Sonraki isteklerde cookie otomatik gönderilir7. Server session'ı doğrular ve kullanıcıya erişim verir🔐 Session Tabanlı Authentication
Section titled “🔐 Session Tabanlı Authentication”Session tabanlı auth, kullanıcı bilgilerinin sunucuda saklandığı yöntemdir.
Session Kurulumu
Section titled “Session Kurulumu”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: '/', }, })}Login Server Function
Section titled “Login Server Function”import { createServerFn } from '@tanstack/react-start'import { z } from 'zod'import { getSession } from './session'
// Mock kullanıcı veritabanıconst KULLANICILAR = []
// Login functionexport 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, }, } })Logout Function
Section titled “Logout Function”// src/lib/auth.ts (devam)
export const logout = createServerFn({ method: 'POST' }) .handler(async () => { const session = await getSession() await session.clear()
return { basari: true } })Mevcut Kullanıcıyı Alma
Section titled “Mevcut Kullanıcıyı Alma”// 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, } })🎯 Login Sayfası Oluşturma
Section titled “🎯 Login Sayfası Oluşturma”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)} 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ı: [email protected] / 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.
Auth Check Middleware
Section titled “Auth Check Middleware”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, }, }) })Protected Route Kullanımı
Section titled “Protected Route Kullanımı”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> )}Daha Basit Yöntem: Loader’da Kontrol
Section titled “Daha Basit Yöntem: Loader’da Kontrol”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> )}📋 Role-Based Access Control (RBAC)
Section titled “📋 Role-Based Access Control (RBAC)”RBAC, kullanıcı rolüne göre yetki kontrolü yapar.
Rol Kontrolü Utility
Section titled “Rol Kontrolü Utility”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, }, }) })}Admin-Only Route
Section titled “Admin-Only Route”import { createFileRoute } from '@tanstack/react-router'import { requireRole } from '../../lib/auth'
// Sadece adminler erişebilirexport 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> )}Moderator veya Admin Route
Section titled “Moderator veya Admin Route”import { createFileRoute } from '@tanstack/react-router'import { requireRole } from '../../lib/auth'
// Hem admin hem moderator erişebilirexport 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> )}Client-Side Rol Kontrolü
Section titled “Client-Side Rol Kontrolü”// Bazen bileşen içinde rol kontrolü gerekebilirfunction 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 Token Authentication
Section titled “🔑 JWT Token Authentication”JWT (JSON Web Token), kullanıcı bilgilerinin token içinde saklandığı stateless auth yöntemidir.
JWT Utility
Section titled “JWT Utility”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 }}JWT ile Login
Section titled “JWT ile Login”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 Entegrasyonu
Section titled “🎯 OAuth Entegrasyonu”OAuth, Google, GitHub gibi üçüncü parti servislerle giriş yapmayı sağlar.
Google OAuth Örneği
Section titled “Google OAuth Örneği”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 } })Callback Route
Section titled “Callback Route”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' }) },})🎨 Pratik Örnek: Tam Auth Sistemi
Section titled “🎨 Pratik Örnek: Tam Auth Sistemi”Hadi öğrendiklerimizle tam bir auth sistemi oluşturalım!
Auth Context Bileşeni
Section titled “Auth Context Bileşeni”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}Header Bileşeni
Section titled “Header Bileşeni”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> )}✅ Ders 8 Özeti
Section titled “✅ Ders 8 Özeti”Bu derste öğrendiklerimiz:
| Konu | Açıklama |
|---|---|
| Authentication | Kimlik doğrulama (“Kimsin?”) |
| Authorization | Yetkilendirme (“Ne yapabilirsin?“) |
| useSession() | Session yönetimi |
| Session-based auth | Sunucuda session saklama |
| JWT | Token-based auth |
| Protected routes | Giriş gerektiren sayfalar |
| RBAC | Role-based access control |
| OAuth | Üçüncü parti auth (Google, GitHub) |
Auth Stratejileri
Section titled “Auth Stratejileri”| Strateji | Avantaj | Dezavantaj |
|---|---|---|
| Session | Güvenli, sunucu kontrolü | Sunucu hafıza kullanımı |
| JWT | Stateless, ölçeklenebilir | Token iptali zor |
| OAuth | Kolay kullanım | Üçüncü parti bağımlılığı |
📝 Alıştırmalar
Section titled “📝 Alıştırmalar”Alıştırma 1: Register Sayfası
Section titled “Alıştırma 1: Register Sayfası”Kullanıcı kayıt sayfası oluşturun:
// Email, şifre, şifre tekrar alanları// Kayıt başarılıysa otomatik girişAlıştırma 2: Şifre Sıfırlama
Section titled “Alıştırma 2: Şifre Sıfırlama”Şifremi unuttum akışı oluşturun:
// 1. Email iste// 2. Reset linki gönder (mock)// 3. Yeni şifre formu// 4. Şifre güncelleAlıştırma 3: Admin Koruması
Section titled “Alıştırma 3: Admin Koruması”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
💬 Sorularınız?
Section titled “💬 Sorularınız?”- Session vs JWT hangisi daha iyi?
- Token nasıl iptal edilir (JWT)?
- OAuth güvenliği nasıl sağlanır?
Bir sonraki derste görüşmek üzere! 👋