Bu sitenin giriş sistemini kurarken ilk refleksim herkesinkiyle aynıydı: login isteği at, dönen JWT'yi localStorage'a koy, her istekte header'a ekle. Üç satır kod, çalışıyor da. Sonra kendime şu soruyu sordum: bu token'ı benden başka kim okuyabilir?
# tehdit_modeli
Cevap rahatsız edici: sayfada çalışan her JavaScript. localStorage'ın XSS'e karşı hiçbir savunması yok — enjekte edilen tek satırlık bir script, token'ı alıp istediği yere gönderebilir. "Benim sitemde XSS mi olacak?" diyorsanız, saldırı yüzeyi sadece sizin yazdığınız kod değil: npm'den gelen her bağımlılık, onların bağımlılıkları, bir gün eklediğiniz üçüncü parti analytics script'i... Supply-chain saldırılarında ele geçirilen popüler paketlerin ilk yaptığı şeylerden biri localStorage'ı süpürmek.
httpOnly cookie ise adı üstünde: JavaScript'e görünmez. document.cookie yazdırın, orada yok. XSS hâlâ kötü bir gündür ama saldırgan token'ı çalamaz.
# desen_bff
Benim kurduğum yapı BFF (Backend-for-Frontend) deseninin sadeleştirilmiş hali. Kural tek cümle: token tarayıcıya hiç inmez.
Giriş formu doğrudan .NET API'ye değil, Next.js'in kendi route handler'ına POST eder. Handler, API'den JWT'yi alır ve httpOnly cookie olarak yazar:
// app/api/auth/login/route.ts
export async function POST(req: Request) {
const res = await fetch(`${API_BASE}/api/auth/login`, {
method: "POST",
body: JSON.stringify(await req.json()),
headers: { "Content-Type": "application/json" },
});
if (!res.ok) return NextResponse.json({ message: "giriş başarısız" }, { status: 400 });
const { token, role } = await res.json();
const out = NextResponse.json({ role });
out.cookies.set("hd-token", token, {
httpOnly: true, secure: true, sameSite: "lax", maxAge: 60 * 60 * 12,
});
return out;
}
# sunucuda_bearer
Peki korumalı veriyi kim çekiyor? Server component'ler ve server action'lar. İkisi de sunucuda çalıştığı için cookie'yi okuyup API'ye Bearer olarak iletebiliyorlar — token yine tarayıcı JavaScript'ine değmeden:
// lib/server-api.ts (sunucuda çalışır)
import { cookies } from "next/headers";
export async function adminGet(path: string) {
const token = cookies().get("hd-token")?.value;
return fetch(`${API_BASE}${path}`, {
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
});
}
# middleware_sadece_kapidaki_gorevli
Next.js middleware'i /panel gibi sayfaları koruyor ama bunu bir güvenlik sınırı sanmamak lazım. Middleware UX içindir: giriş yapmamış kullanıcıyı kibarca login'e yönlendirir. Asıl koruma .NET tarafında, her admin endpoint'indeki [Authorize(Roles = "Admin")]'de. Rol bilgisini tutan okunabilir bir cookie'yi kurcalasanız bile API 403 döner — kapıdaki görevliyi kandırabilirsiniz, kasadaki kilidi değil.
# kucuk_ama_onemli
Tek başına desen yetmiyor; şunlar da pakete dahil:
SameSite=Lax — cookie tabanlı auth'ta CSRF'i ciddiye alın; mutasyonlarım server action olduğu ve cross-site POST'larda cookie gitmediği için buradaki risk daralıyor. Secure — üretimde cookie sadece HTTPS ile taşınır. Rate limit — login endpoint'i dakikada birkaç denemeyle sınırlı; şifre deneme botlarının işini zorlaştırır. JWT anahtarı — koddaki varsayılan anahtarla üretime çıkmayı uygulama kendisi reddediyor: Jwt__Key env'den gelmezse Production'da hiç açılmıyor. En sevdiğim güvenlik önlemi, unutulması imkânsız olanı.
# ozet
JWT'nin yeri tarayıcı depolaması değil. Next.js kullanıyorsanız BFF kurmak yarım günlük iş: token'ı route handler'da httpOnly cookie'ye yazın, korumalı çağrıları sunucuda yapın, middleware'i UX için kullanın, asıl yetkiyi API'de doğrulayın. Bu sitede şu an okuduğunuz sayfa dahil her şey bu düzenle çalışıyor.