Modern bir web uygulamasını sadece "statik içeriklerden" ayıran en temel özellik, kişiselleştirilmiş bir deneyim sunabilmesidir. Herhangi bir kullanıcı, popüler bir e-ticaret sitesine girdiğinde ürünleri inceleyebilir, yorumları okuyabilir ve fiyatları görebilir; ancak ne zaman ki bir ürünü sepete atmak veya geçmiş siparişlerine bakmak isterse, uygulama ondan bir "kimlik" ibraz etmesini bekler. İşte bu noktada devreye giren Kimlik Doğrulama (Authentication), uygulamanın kapısındaki bir güvenlik protokolüdür ve temelde tek bir kritik soruya yanıt arar: "Sen, iddia ettiğin kişi misin?"
Kimlik doğrulamanın arkasındaki asıl fikir, uygulamanın sunduğu eylemleri "herkese açık" (public) ve "korumalı" (protected) olarak ikiye ayırmaktır. Anonim bir ziyaretçi ile sisteme kayıtlı bir kullanıcı arasındaki bu ayrım, sadece güvenliği sağlamakla kalmaz; aynı zamanda sunucunun veritabanındaki binlerce veri arasından (sepetler, siparişler, özel bilgiler) hangisinin o anki kullanıcıyla eşleştiğini belirlemesine olanak tanır. Kısacası kimlik doğrulama, anonim bir "ziyaretçiyi", uygulama ekosistemi içinde tanınan, yetkileri belirlenmiş ve geçmişi olan bir "aktöre" dönüştürme sürecidir.
Bölüm: Kimlik Doğrulama Nasıl Uygulanır? (Adım Adım Süreç)
Kimlik doğrulama, sadece bir form doldurmaktan ibaret değildir; tarayıcı, sunucu ve veritabanı arasında dönen kusursuz bir protokol zinciridir. Geleneksel web uygulamalarında (EJS, Handlebars gibi motorlar kullanan yapılarda) bu süreç "Oturum Tabanlı Kimlik Doğrulama" (Session-based Authentication) olarak adlandırılır.
1. İstek: Kimlik Bilgilerinin Gönderilmesi
Her şey kullanıcının giriş sayfasındaki formu doldurmasıyla başlar. Kullanıcı e-posta ve şifresini içeren bir POST isteğini sunucuya gönderir.
// routes/auth.js
router.post('/login', (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
// ... doğrulama mantığı burada başlar
});2. Doğrulama: Veritabanı Kontrolü
Sunucu, gelen e-posta adresini veritabanındaki kayıtlarla karşılaştırır. Eğer böyle bir kullanıcı varsa, girilen şifrenin veritabanındaki şifreyle (güvenlik için genellikle hash'lenmiş haliyle) eşleşip eşleşmediğine bakar.
3. Bağlantı Kurma: Oturumun (Session) Oluşturulması
İşte burası en kritik adımdır. Bilgiler doğru olsa bile, sunucu sadece "Evet, bu doğru" deyip geçemez. HTTP’nin stateless (durum bilgisiz) doğası gereği, sunucu bu başarıyı bir saniye sonra unutacaktır.
Çözüm: Sunucu, bu spesifik kullanıcı için bellekte veya veritabanında bir Oturum (Session) oluşturur.
Bu oturumun içine "Bu kullanıcı başarıyla giriş yaptı" bilgisini (
isLoggedIn: true) ve kullanıcının benzersiz ID'sini kaydeder.
4. Tanıma: Çerez (Cookie) ve Başarı Yanıtı
Sunucu, oluşturduğu bu oturumun anahtarını (Session ID) bir çerez aracılığıyla tarayıcıya geri gönderir. Tarayıcı bu anahtarı cebine koyar ve bir sonraki isteğinde (örneğin sepetine gitmek istediğinde) bu anahtarı sunucuya gösterir. Sunucu anahtarı alır, kasayı açar ve "Evet, bu User A, geçebilirsin" der.
5. Sonuç: Korumalı Rotalara Erişim
Artık kullanıcı her yeni sayfa isteğinde bu çerezi otomatik olarak gönderdiği için, sunucu tarafındaki ara yazılımlar (middleware) oturumun geçerliliğini kontrol edebilir ve kısıtlı kaynaklara erişim izni verebilir.
Bölüm: Kullanıcı Kayıt (Signup) Mantığı: Veritabanına İlk Adım
Kimlik doğrulama sisteminin ilk fiziksel adımı, kullanıcıyı veritabanına güvenli bir şekilde dahil etmektir. Bir kayıt (signup) süreci basit görünse de, arka planda üç kritik kontrol noktasından geçer:
1. Veri Ekstraksiyonu ve İlk Kontroller
Kullanıcı formu doldurup gönderdiğinde, sunucu tarafında req.body üzerinden e-posta ve şifre gibi bilgileri ayıklarız. Bu aşamada normalde "Validation" (Doğrulama) mekanizmaları devreye girer: "E-posta formatı doğru mu?", "Şifreler birbiriyle eşleşiyor mu?" gibi kontroller yapılır.
2. Çakışma Kontrolü: "Unique User" Problemi
Veritabanımızda aynı e-posta adresiyle iki farklı kullanıcı olmamalıdır. Bu sorunu çözmek için iki yöntem izlenebilir:
Veritabanı Seviyesi: MongoDB'de e-posta alanına
unique: trueindeksi eklemek.Uygulama Seviyesi: Kayıt işleminden hemen önce veritabanında
User.findOne({ email: email })sorgusuyla bu kullanıcının daha önce oluşturulup oluşturulmadığını kontrol etmek. Eğer kullanıcı zaten varsa, işlem iptal edilir ve kullanıcı kayıt sayfasına geri yönlendirilir.
3. Kullanıcı Modelinin Oluşturulması ve Kayıt
Eğer kullanıcı benzersizse, Mongoose modelimiz aracılığıyla yeni bir kullanıcı nesnesi oluşturulur. Bu aşamada kullanıcıya boş bir sepet (cart) atanır ve .save() metoduyla veritabanına kalıcı olarak yazılır. İşlem başarıyla tamamlandığında kullanıcı, bir sonraki adım olan Giriş (Login) sayfasına yönlendirilir.
// controllers/auth.js
const User = require('../models/user');
exports.postSignup = (req, res, next) => {
const { email, password } = req.body;
const user = new User({
email: email,
password: password, // Şimdilik plain text (Hashing başlığından önce)
cart: { items: [] }
});
return user.save().then(result => res.redirect('/login'));
};Şifre Güvenliği - Neden "Hashing" Kullanmalıyız?
Kullanıcı kaydını gerçekleştirdik ancak burada hayati bir soru ortaya çıkıyor: Veritabanına baktığımızda ne görüyoruz? Eğer şifreleri "Plain Text" (Düz Metin) olarak saklıyorsak, bu hem etik hem de teknik olarak büyük bir güvenlik felaketidir.
1. Hashing vs. Şifreleme (Encryption)
Geliştiriciler arasında sıkça karıştırılan bu iki kavram arasındaki farkı bilmek kritiktir:
Şifreleme: Çift yönlüdür; bir anahtarla kilitlenir ve geri açılabilir.
Hashing: Tek yönlüdür; bir veri hash'lendiğinde orijinal haline geri döndürülemez.
2. Bcrypt ile Güvenli Veri Saklama
Şifreleri korumak için endüstri standardı olan bcrypt kütüphanesini kullanıyoruz. Bcrypt, şifreyi hash'lemeden önce ona rastgele karakterler ekleyerek (Salt/Tuzlama) "Rainbow Table" gibi saldırı yöntemlerini imkansız hale getirir. Kayıt anında şifreyi doğrudan kaydetmek yerine, önce hash'leyip ardından veritabanına yazmalıyız.
// Şifreyi 12 tur dönerek hash'liyoruz
bcrypt.hash(password, 12)
.then(hashedPassword => {
const user = new User({
email: email,
password: hashedPassword, // Veritabanına hashlenmiş halini kaydediyoruz
cart: { items: [] }
});
return user.save();
});Giriş (Login) ve Doğrulama
Şifreler hash'lendiği için onları geri döndürüp kontrol edemeyiz. Bunun yerine, kullanıcı giriş yapmaya çalıştığında girdiği "düz metin" şifreyi alır ve bcrypt.compare() metoduna göndeririz. Bcrypt, kullanıcının o an girdiği şifreyi aynı algoritma ile tekrar işler ve veritabanındaki hash ile matematiksel olarak eşleşip eşleşmediğine bakar. Eğer eşleşiyorsa, sunucu güvenli bir şekilde oturumu (Session) başlatır.
// controllers/auth.js
User.findOne({ email: email })
.then(user => {
if (!user) return res.redirect('/login');
return bcrypt.compare(password, user.password)
.then(doMatch => {
if (doMatch) {
req.session.isLoggedIn = true;
req.session.user = user;
// Yönlendirmeden önce session'ın kaydedildiğinden emin oluyoruz
return req.session.save(err => res.redirect('/'));
}
res.redirect('/login');
});
});
Rota Koruması (Authorization)
Önceki adımlarda kullanıcıyı kaydettik ve giriş yapmasını sağladık. Ancak çok kritik bir güvenlik açığımız var: Eğer bir kullanıcı giriş yapmadan tarayıcı adres çubuğuna doğrudan /admin/add-product yazarsa ne olur?
Eğer rotalarımızı korumazsak, kullanıcı butonları görmese bile bu sayfaya erişebilir. İşte bu noktada Rota Koruması devreye girer.
1. Neden Middleware (Ara Yazılım) Kullanmalıyız?
Her denetleyici (controller) içine tek tek if (!req.session.isLoggedIn) { ... } yazmak ölçeklenebilir bir çözüm değildir. Eğer 50 tane korumalı rotanız varsa, aynı kodu 50 kez kopyalamanız gerekir. Bunun yerine, bu mantığı tek bir Middleware dosyasında toplayıp, korumak istediğimiz rotalara birer "bekçi" olarak atayabiliriz.
2. isAuth Bekçisini Oluşturmak
Basit bir is-auth.js ara yazılımı, isteğin devam edip etmeyeceğine karar veren bir trafik polisi gibi çalışır:
module.exports = (req, res, next) => {
if (!req.session.isLoggedIn) {
// Oturum yoksa, isteği durdur ve giriş sayfasına yönlendir
return res.redirect('/login');
}
// Oturum varsa, bir sonraki fonksiyona geçiş izni ver
next();
};3. Rotalara "Bekçi" Atamak
Express.js'te rotalar soldan sağa doğru işlenir. Bu sayede isAuth fonksiyonunu, asıl denetleyici eyleminden önce bir argüman olarak ekleyebiliriz:
// İstek önce isAuth'a gider, onay alırsa getAddProduct çalışır.
router.get('/add-product', isAuth, adminController.getAddProduct);Artık kullanıcı giriş yapmadığı sürece /admin sayfalarına, sepetine (/cart) veya siparişlerine (/orders) manuel olarak ulaşmaya çalışsa bile sistem onu otomatik olarak giriş sayfasına geri fırlatacaktır.
Web'in Hafıza Sorunundan Tam Güvenliğe
Bu yazı boyunca, web'in doğuştan gelen "stateless" (unutkan) yapısını Cookies ve Sessions ile nasıl aştığımızı, ardından bu yapıyı kullanarak nasıl güvenli bir Kimlik Doğrulama sistemi kurduğumuzu inceledik.
Cookies: Tarayıcıdaki kimlik kartımız.
Sessions: Sunucudaki güvenli kasamız.
Hashing (Bcrypt): Şifrelerin okunamaz hale getirilmesi.
Middleware (isAuth): Rotalarımızın güvenliğini sağlayan bekçilerimiz.
Güvenli bir web uygulaması inşa etmek sadece kod yazmak değil, aynı zamanda kullanıcı verilerini ve uygulama rotalarını doğru mimariyle koruma altına almaktır.