Takip et

Auth Series #5: Passport.js ile Yetkilendirme Uygulaması

Modern web uygulamaları, kullanıcıların kişisel verilerinden finansal işlemlere kadar geniş bir yelpazede hassas bilgileri barındırır. Bu bilgilerin korunması, sadece etik bir sorumluluk değil, aynı zamanda yasal bir zorunluluktur. Peki, bir kullanıcı sisteme giriş yaptığında, yani kimliği doğrulandığında, hangi kaynaklara erişebileceğine veya hangi işlemleri yapabileceğine nasıl karar veririz? İşte bu noktada yetkilendirme (authorization) devreye girer. Yetkilendirme, kullanıcıların belirli kaynaklara veya işlevlere erişim haklarını tanımlayan ve uygulayan kritik bir güvenlik mekanizmasıdır. Doğru uygulanmayan bir yetkilendirme sistemi, veri ihlallerine, yetkisiz erişimlere ve potansiyel olarak çok ciddi güvenlik zafiyetlerine yol açabilir. Bu makalede, Node.js ekosisteminin popüler kimlik doğrulama middleware’i olan Passport.js’i kullanarak yetkilendirmeyi nasıl başarılı bir şekilde uygulayacağımızı derinlemesine inceleyeceğiz. Amacımız, uygulamalarınızın sadece kimlik doğrulamada değil, aynı zamanda yetkilendirme katmanında da sağlam ve güvenli olmasını sağlamaktır.

Passport.js, temelde bir kimlik doğrulama kütüphanesi olsa da, esnek yapısı sayesinde yetkilendirme mantığını entegre etmek için güçlü bir temel sunar. Özellikle Express.js tabanlı uygulamalarda yaygın olarak kullanılan Passport.js, farklı kimlik doğrulama stratejilerini (yerel, OAuth, JWT vb.) tek bir çatı altında toplamasıyla bilinir. Ancak yetkilendirme, kimlik doğrulamadan sonra gelen bir adımdır ve “Kimsin?” sorusundan çok “Ne yapabilirsin?” sorusuna yanıt verir. Bu nedenle, Passport.js’i sadece kullanıcıyı tanımak için değil, aynı zamanda o kullanıcının sistemdeki rolünü ve ayrıcalıklarını belirlemek için nasıl kullanabileceğimizi anlamak büyük önem taşır. Bu kapsamlı rehberde, temelden ileri seviyeye kadar çeşitli yetkilendirme modellerini ele alacak, gerçek dünya senaryolarıyla pekiştirecek ve uygulamalı kod örnekleri sunarak bilgilerinizi pekiştireceğiz.

Web geliştirme dünyasında güvenlik, sürekli evrilen bir alandır ve yetkilendirme mekanizmaları da bu değişimin merkezinde yer alır. Özellikle kullanıcı deneyiminden ödün vermeden güvenliği en üst düzeye çıkarmak, her geliştiricinin hedefidir. Bu makale boyunca, adım adım ilerleyerek, Passport.js’in sunduğu olanakları en verimli şekilde kullanarak, uygulamalarınıza sağlam bir yetkilendirme katmanı nasıl ekleyeceğinizi keşfedeceksiniz. Kendi uygulamalarınızda uygulayabileceğiniz pratik bilgilerle donatılırken, aynı zamanda olası güvenlik açıklarına karşı proaktif önlemler almanın yollarını da öğreneceksiniz. Hazırsanız, Passport.js ile yetkilendirmenin inceliklerini keşfetmek için bu yolculuğa başlayalım.

Temel Kavramlar: Kimlik Doğrulama ve Yetkilendirme Arasındaki Fark Nedir?

Güvenli bir uygulamanın temel taşlarından ikisi olan kimlik doğrulama (authentication) ve yetkilendirme (authorization) kavramları, sıklıkla birbirine karıştırılsa da, aslında farklı rolleri ve sorumlulukları vardır. Bu iki kavramı net bir şekilde anlamak, sağlam güvenlik mekanizmaları inşa etmenin ilk adımıdır. Dolayısıyla, Passport.js gibi bir aracı yetkilendirme süreçlerine dahil etmeden önce, bu temel farkları iyice kavramak gereklidir.

Kimlik Doğrulama (Authentication): Bu süreç, bir kullanıcının veya sistemin iddia ettiği kişi veya sistem olup olmadığını doğrular. Basitçe ifade etmek gerekirse, “Sen kim olduğunu iddia ediyorsun ve bunu kanıtlayabilir misin?” sorusuna yanıt arar. Genellikle kullanıcı adı ve parola kombinasyonu, parmak izi, yüz tanıma veya çok faktörlü kimlik doğrulama (MFA) gibi yöntemlerle gerçekleştirilir. Başarılı bir kimlik doğrulama sonucunda, sistem kullanıcının kimliğini onaylar ve genellikle bir oturum (session) veya bir erişim belirteci (token) oluşturur. Passport.js, tam olarak bu adıma odaklanır ve çeşitli “stratejiler” aracılığıyla kimlik doğrulama mekanizmalarını kolayca entegre etmemizi sağlar. Örneğin, bir kullanıcının sisteme doğru kullanıcı adı ve şifreyle giriş yapıp yapmadığını doğrulamak kimlik doğrulama işlemidir.

Yetkilendirme (Authorization): Kimlik doğrulamanın başarıyla tamamlanmasının ardından devreye giren yetkilendirme ise, kimliği doğrulanmış bir kullanıcının belirli bir kaynağa erişim izni olup olmadığını veya belirli bir işlemi gerçekleştirip gerçekleştiremeyeceğini belirler. Diğer bir deyişle, “Kim olduğunu biliyoruz; şimdi, ne yapmana izin var?” sorusuna yanıt verir. Yetkilendirme, genellikle kullanıcının rolü (örneğin, yönetici, editör, okuyucu), sahip olduğu ayrıcalıklar veya belirli kaynaklarla olan ilişkisi gibi faktörlere dayanır. Bir kullanıcının bir gönderiyi silebilmesi, bir ayarı değiştirebilmesi veya belirli bir rapora erişebilmesi, yetkilendirme ile kontrol edilen eylemlerdir. Passport.js doğrudan bir yetkilendirme kütüphanesi olmasa da, kimlik doğrulama sonrası kullanıcı bilgilerini req.user nesnesine ekleyerek, bu bilgileri yetkilendirme middleware’ları içinde kullanmamıza olanak tanır. Örneğin, bir “yönetici” rolüne sahip kullanıcının sistemdeki tüm kullanıcıları düzenleyebilmesi, ancak “editör” rolüne sahip bir kullanıcının yalnızca kendi gönderilerini düzenleyebilmesi yetkilendirme ile sağlanır.

Bu iki kavram arasındaki ilişkiyi anlamak, güvenlik mimarisini doğru bir şekilde tasarlamak için hayati öneme sahiptir. Kimlik doğrulama olmadan yetkilendirme anlamsızdır, çünkü sistem kimin yetkilendirildiğini bilmez. Benzer şekilde, yetkilendirme olmadan kimlik doğrulama, kullanıcıya sistemi kullanma izni verir ancak hangi sınırlar içinde kullanabileceğini belirlemez, bu da potansiyel güvenlik riskleri yaratır. Dolayısıyla, her iki sürecin de birbiriyle uyumlu ve sağlam bir şekilde çalışması gerekmektedir.

Özetle, kimlik doğrulama giriş kapısıdır; kimin içeri girebileceğine karar verir. Yetkilendirme ise içerideki odaların kapılarını açma yetkisidir; içeri giren kişinin hangi odalara girebileceğini veya hangi eşyalara dokunabileceğini belirler. Passport.js ile bu iki katmanı bir arada ele alacak, öncelikle kimlik doğrulamayı kuracak, ardından yetkilendirme mantığını nasıl entegre edeceğimizi adım adım göreceğiz. Bu ayrım, hem kodunuzu daha okunabilir ve yönetilebilir hale getirir hem de uygulamanızın güvenlik duruşunu önemli ölçüde güçlendirir.

Passport.js ile Temel Kimlik Doğrulama Nasıl Yapılır?

Passport.js, Node.js uygulamalarında kimlik doğrulama işlemlerini basitleştiren güçlü ve esnek bir middleware kütüphanesidir. Farklı kimlik doğrulama mekanizmaları için “stratejiler” sunarak, geliştiricilerin güvenlik katmanını kolayca uygulamalarına olanak tanır. Yetkilendirmeye geçmeden önce, bir kullanıcının kimliğini başarıyla doğrulamanın temellerini atmamız gerekiyor. Bu bölümde, Passport.js’i kullanarak yerel bir kimlik doğrulama stratejisini (kullanıcı adı ve parola) nasıl kuracağımızı adım adım inceleyeceğiz. Bu adımlar, uygulamanızın geri kalanında yetkilendirme kararları almak için gerekli kullanıcı bilgilerini sağlayacaktır.

1. Gerekli Paketlerin Kurulumu:

Öncelikle Express.js tabanlı bir uygulama için gerekli paketleri kurmamız gerekiyor. Eğer Express uygulamanız yoksa, hızlıca bir tane oluşturabilirsiniz. Ardından, Passport.js, yerel strateji ve oturum yönetimi için ek paketleri yükleyelim:


npm install express passport passport-local express-session
  

2. Temel Express Uygulaması ve Passport Entegrasyonu:

Bir app.js veya server.js dosyasında Express uygulamanızı başlatın ve Passport'ı entegre edin. Oturum yönetimi için express-session kullanacağız, bu da Passport'ın kullanıcı bilgilerini oturumda saklamasını sağlayacak:


const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
const bcrypt = require('bcryptjs'); // Parola hashleme için

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware'lar
app.use(express.json()); // JSON body parsing
app.use(express.urlencoded({ extended: true })); // URL-encoded body parsing

// Oturum Yapılandırması
app.use(session({
    secret: 'cokgizlibiranahtar', // Oturum verilerini şifrelemek için kullanılan gizli anahtar
    resave: false,               // Oturumda değişiklik yapılmasa bile tekrar kaydetme
    saveUninitialized: false,    // Yeni oluşturulan ancak değiştirilmemiş oturumları kaydetme
    cookie: { maxAge: 1000 * 60 * 60 * 24 } // 1 gün
}));

// Passport'ı başlatma ve oturum kullanma
app.use(passport.initialize());
app.use(passport.session());
  

3. Passport.js Yerel Stratejinin Yapılandırılması:

Yerel strateji, kullanıcının gönderdiği kullanıcı adı ve parolayı doğrulamak için kullanılır. Bu stratejiyi yapılandırırken, veritabanınızdaki kullanıcıları kontrol eden bir fonksiyon sağlamalısınız. Şimdilik basit bir örnek için statik bir kullanıcı array'i kullanalım:


const users = [
    { id: '1', username: 'admin', passwordHash: bcrypt.hashSync('adminparola', 10), role: 'admin' },
    { id: '2', username: 'editor', passwordHash: bcrypt.hashSync('editorparola', 10), role: 'editor' },
    { id: '3', username: 'guest', passwordHash: bcrypt.hashSync('guestparola', 10), role: 'guest' }
];

passport.use(new LocalStrategy(
    async (username, password, done) => {
        try {
            const user = users.find(u => u.username === username);

            if (!user) {
                return done(null, false, { message: 'Yanlış kullanıcı adı.' });
            }

            const isMatch = await bcrypt.compare(password, user.passwordHash);
            if (!isMatch) {
                return done(null, false, { message: 'Yanlış parola.' });
            }

            return done(null, user); // Kimlik doğrulama başarılı
        } catch (err) {
            return done(err);
        }
    }
));
  

4. Kullanıcı Bilgilerinin Oturumda Saklanması ve Geri Yüklenmesi:

Passport, kullanıcının kimlik doğrulandıktan sonra oturumda hangi verilerinin saklanacağını ve her istekte oturumdan nasıl geri yükleneceğini bilmelidir. Bu, serializeUser ve deserializeUser fonksiyonlarıyla yapılır:


passport.serializeUser((user, done) => {
    done(null, user.id); // Sadece kullanıcı ID'sini oturumda sakla
});

passport.deserializeUser((id, done) => {
    const user = users.find(u => u.id === id);
    if (user) {
        done(null, user); // Kullanıcı ID'si ile tüm kullanıcı nesnesini geri yükle
    } else {
        done(new Error('Kullanıcı bulunamadı.'));
    }
});
  

5. Kimlik Doğrulama Rotasının Oluşturulması:

Şimdi, kullanıcıların giriş yapabileceği bir POST rotası oluşturalım:


// Giriş rotası
app.post('/login', passport.authenticate('local', {
    successRedirect: '/profile', // Başarılı giriş sonrası yönlendirme
    failureRedirect: '/login-fail', // Başarısız giriş sonrası yönlendirme
    failureFlash: true // Hata mesajlarını flash'a kaydet (connect-flash paketi gerektirir)
}));

// Profil rotası (korumalı)
app.get('/profile', (req, res) => {
    if (req.isAuthenticated()) { // Passport'ın sağladığı bir metod
        res.send(Merhaba, ${req.user.username}! Rolün: ${req.user.role});
    } else {
        res.redirect('/login-fail');
    }
});

// Giriş başarısız rotası
app.get('/login-fail', (req, res) => {
    res.send('Giriş başarısız oldu. Lütfen kullanıcı adı ve parolanızı kontrol edin.');
});

// Çıkış rotası
app.get('/logout', (req, res, next) => {
    req.logout((err) => { // Passport'ın sağladığı logout metodu
        if (err) { return next(err); }
        res.redirect('/');
    });
});

app.get('/', (req, res) => {
    res.send('Ana Sayfa. Lütfen giriş yapın: /login adresine POST isteği ile.');
});

app.listen(PORT, () => {
    console.log(Sunucu http://localhost:${PORT} adresinde çalışıyor.);
});
  

Bu temel kurulumla, kullanıcı adı ve parola ile kimlik doğrulama işlemi tamamlanmış olur. req.isAuthenticated() metodu, kullanıcının oturumda kimliği doğrulanmış olup olmadığını kontrol ederken, başarılı kimlik doğrulamasından sonra req.user nesnesi, oturumdan yüklenen kullanıcı bilgilerini (bu örnekte id, username, passwordHash, role) içerir. İşte bu req.user nesnesi, yetkilendirme kararlarını vermek için kullanacağımız anahtar bilgidir. Bir sonraki bölümde, bu bilgiyi kullanarak role dayalı yetkilendirmeyi nasıl uygulayacağımızı göreceğiz.

Yetkilendirme İçin Rol Tabanlı Erişim Kontrolü (RBAC) Nasıl Uygulanır?

Kimlik doğrulama, bir kullanıcının sisteme kim olarak girdiğini belirler. Ancak "admin" rolündeki bir kullanıcının her şeye erişimi varken, "editor" rolündeki bir kullanıcının yalnızca belirli içerikleri düzenlemesine izin verilmesi gerekir. İşte bu noktada Rol Tabanlı Erişim Kontrolü (Role-Based Access Control - RBAC) devreye girer. RBAC, yetkilendirme modellerinin en yaygın ve anlaşılır olanlarından biridir. Kullanıcılara doğrudan izinler vermek yerine, onları bir veya daha fazla role atarız ve bu rollere belirli izinler tanımlarız. Böylece, bir kullanıcının erişimini yönetmek için sadece rolünü değiştirmek yeterli olur.

Passport.js ile RBAC uygulamak, kimlik doğrulama sonrası req.user nesnesine eklenen rol bilgisini kullanarak özel bir middleware yazmayı içerir. Bu middleware, her isteğin hedeflenen rotaya ulaşmadan önce kullanıcının gerekli role sahip olup olmadığını kontrol edecektir. Şimdi adım adım RBAC'ı nasıl entegre edeceğimizi görelim.

1. Kullanıcı Modeline Rol Eklemek:

Önceki örnekteki statik users dizisine zaten role alanını eklemiştik. Gerçek bir uygulamada, bu role bilgisi veritabanınızdaki User modelinizde saklanmalıdır. Örneğin:


// Örnek kullanıcı modeli (gerçekte veritabanında saklanır)
const users = [
    { id: '1', username: 'admin', passwordHash: 'hashed_admin_password', role: 'admin' },
    { id: '2', username: 'editor', passwordHash: 'hashed_editor_password', role: 'editor' },
    { id: '3', username: 'guest', passwordHash: 'hashed_guest_password', role: 'guest' }
];
  

2. Yetkilendirme Middleware'ı Oluşturma:

Şimdi, belirli bir rol veya rollere sahip kullanıcıların bir rotaya erişmesini sağlayan dinamik bir middleware fonksiyonu yazalım. Bu fonksiyon, bir veya daha fazla rol alacak ve req.user nesnesindeki role ile karşılaştıracaktır.


// isAuthorized middleware: Kullanıcının giriş yapıp yapmadığını kontrol eder
function isAuthorized(req, res, next) {
    if (req.isAuthenticated()) {
        return next(); // Kullanıcı oturum açmışsa devam et
    }
    res.status(401).send('Yetkilendirilmemiş: Lütfen giriş yapın.');
}

// hasRole middleware: Kullanıcının belirli bir role sahip olup olmadığını kontrol eder
function hasRole(roles) {
    return (req, res, next) => {
        if (!req.user) {
            return res.status(403).send('Yasak: Kullanıcı bilgisi mevcut değil. Giriş yapmış olsanız bile yetkiniz olmayabilir.');
        }

        const userRole = req.user.role;
        // Eğer roller tek bir string olarak gelirse array'e çevir
        const allowedRoles = Array.isArray(roles) ? roles : [roles];

        if (allowedRoles.includes(userRole)) {
            return next(); // Kullanıcı gerekli role sahipse devam et
        } else {
            res.status(403).send(Yasak: Bu kaynağa erişim için gerekli rolünüz yok. Rolünüz: ${userRole}. Gerekli roller: ${allowedRoles.join(', ')});
        }
    };
}
  

Yukarıdaki hasRole middleware'ı, bir veya birden fazla rolü parametre olarak alır. Middleware çalıştırıldığında, req.user.role ile bu rolleri karşılaştırır. Eğer kullanıcının rolü, izin verilen rollerden biriyse, istek bir sonraki middleware'a veya rota işleyicisine geçer. Aksi takdirde, kullanıcıya 403 Forbidden hatası döner.

3. Korumalı Rotları Uygulama:

Artık bu middleware'ı rotalarımıza uygulayabiliriz. Örneğin, yalnızca yöneticilerin erişebileceği bir admin paneli, editörlerin içerik oluşturabileceği bir rota ve tüm giriş yapmış kullanıcıların görebileceği bir profil sayfası oluşturalım:


// ... (Önceki Passport.js kurulum kodları) ...

// Yöneticiye özel rota
app.get('/admin', isAuthorized, hasRole('admin'), (req, res) => {
    res.send(Yönetici Paneli: Merhaba ${req.user.username}! Tüm sistem ayarlarını buradan yönetebilirsiniz.);
});

// Editöre özel rota
app.get('/editor/dashboard', isAuthorized, hasRole(['admin', 'editor']), (req, res) => {
    res.send(Editör Paneli: Merhaba ${req.user.username}! İçeriklerinizi buradan düzenleyebilirsiniz.);
});

// Tüm giriş yapmış kullanıcıların erişebileceği rota
app.get('/dashboard', isAuthorized, (req, res) => {
    res.send(Kullanıcı Paneli: Merhaba ${req.user.username}! Genel bilgilere buradan erişebilirsiniz.);
});

// ... (Diğer rotalar ve sunucu başlatma) ...
  

Bu örnekte, isAuthorized middleware'ını kullanarak önce kullanıcının giriş yapıp yapmadığını kontrol ediyoruz. Eğer giriş yapılmışsa, hasRole middleware'ı ile kullanıcının rolünü doğrularız. '/editor/dashboard' rotası için hasRole(['admin', 'editor']) kullanarak hem yöneticilerin hem de editörlerin bu rotaya erişebilmesini sağlıyoruz. Bu esneklik, RBAC'ın en büyük avantajlarından biridir.

Gerçek Dünya Senaryosu: E-ticaret Platformunda Rol Tabanlı Yetkilendirme

Bir e-ticaret platformu hayal edelim. Bu platformda farklı kullanıcı rolleri ve bu rollere özel erişimler bulunur:

  • Müşteri (customer): Ürünleri görebilir, sipariş verebilir, kendi sipariş geçmişini görüntüleyebilir.
  • Satıcı (seller): Kendi ürünlerini ekleyebilir, güncelleyebilir, silebilir; kendi siparişlerini yönetebilir. Başka bir satıcının ürününü veya siparişini göremez.
  • Yönetici (admin): Tüm ürünleri, tüm siparişleri, tüm kullanıcıları yönetebilir. Satıcılara ürün onayları verebilir.

Bu senaryoda, hasRole middleware'ı ile rotaları kolayca koruyabiliriz:


// Ürün ekleme rotası - sadece satıcılar ve adminler
app.post('/products', isAuthorized, hasRole(['seller', 'admin']), (req, res) => {
    // Ürün ekleme mantığı
    res.send(Ürün eklendi: ${req.body.name} (Ekleyen: ${req.user.username}, Rol: ${req.user.role}));
});

// Tüm siparişleri görüntüleme - sadece adminler
app.get('/orders/all', isAuthorized, hasRole('admin'), (req, res) => {
    // Tüm siparişleri listeleme mantığı
    res.send('Tüm siparişler listeleniyor (Admin).');
});

// Kendi siparişlerini görüntüleme - müşteriler ve satıcılar
app.get('/orders/my', isAuthorized, hasRole(['customer', 'seller']), (req, res) => {
    // Kullanıcının kendi siparişlerini filtreleyerek gösterme mantığı
    res.send(Kendi siparişleriniz listeleniyor (${req.user.username}).);
});
  

RBAC, karmaşık yetkilendirme ihtiyaçlarını basit ve yönetilebilir bir şekilde çözmek için ideal bir yöntemdir. Ancak bazen sadece rol bilgisi yeterli olmayabilir. Bir sonraki bölümde, daha granular kontrol gerektiren "kaynak tabanlı yetkilendirme" senaryolarını inceleyeceğiz.

Daha Gelişmiş Senaryolar: Kaynak Tabanlı Yetkilendirme

Rol Tabanlı Erişim Kontrolü (RBAC) birçok senaryo için yeterli olsa da, bazen yetkilendirme kararlarını sadece kullanıcının rolüne göre değil, aynı zamanda erişmeye çalıştığı kaynağın özelliklerine veya kaynağı kimin oluşturduğuna göre de vermek gerekebilir. İşte bu, kaynak tabanlı yetkilendirme (Resource-Based Authorization) veya bazen nitelik tabanlı erişim kontrolü (Attribute-Based Access Control - ABAC) olarak adlandırılan daha gelişmiş bir modeldir. Bu modelde, bir kullanıcının bir kaynağa (örneğin, bir gönderi, bir dosya, bir yorum) erişip erişemeyeceğine veya üzerinde bir işlem yapıp yapamayacağına karar verirken, kullanıcının kendisi, kaynağın kendisi ve hatta içinde bulunulan ortam hakkında daha fazla bilgi değerlendirilir.

Örnek bir senaryo: Bir blog uygulamasında, bir yazar (editor rolünde) kendi gönderilerini düzenleyebilir veya silebilir, ancak başka bir yazarın gönderilerini düzenleyemez. "Editor" rolü bu kuralı tek başına açıklamaz; aynı rol içinde bile, hangi gönderinin kime ait olduğu önemlidir. Bu durumda, req.user nesnesindeki kullanıcı ID'si ile kaynağın (gönderi) authorId (yazar ID'si) özelliğini karşılaştırmamız gerekir.

1. Kaynak Tabanlı Middleware Oluşturma:

Bu tür bir yetkilendirme için özel bir middleware'a ihtiyacımız var. Bu middleware, isteğin parametrelerinden veya gövdesinden kaynak ID'sini alacak, ardından veritabanından bu kaynağı çekecek ve kullanıcının bu kaynak üzerindeki haklarını kontrol edecektir. İşte bir örnek:


// isOwner middleware: Kullanıcının belirli bir kaynağın sahibi olup olmadığını kontrol eder
function isOwner(model, paramName = 'id', userIdField = 'userId') {
    return async (req, res, next) => {
        if (!req.user) {
            return res.status(403).send('Yasak: Kullanıcı bilgisi mevcut değil.');
        }

        const resourceId = req.params[paramName];
        if (!resourceId) {
            return res.status(400).send('Hata: Kaynak ID\'si sağlanmadı.');
        }

        try {
            // Gerçek uygulamada, bu kısım veritabanı sorgusu olacaktır.
            // Örneğin: const resource = await model.findById(resourceId);
            // Simülasyon için:
            const resources = [
                { id: 'post1', title: 'İlk Gönderi', userId: '2' }, // editor kullanıcısı '2'ye ait
                { id: 'post2', title: 'İkinci Gönderi', userId: '3' }  // guest kullanıcısı '3'e ait
            ];
            const resource = resources.find(r => r.id === resourceId);

            if (!resource) {
                return res.status(404).send('Kaynak bulunamadı.');
            }

            // Kullanıcının ID'si ile kaynağın sahibi ID'sini karşılaştır
            if (resource[userIdField] === req.user.id) {
                // Kaynağı sonraki middleware'lar veya rotalar için req nesnesine ekleyebiliriz
                req.resource = resource;
                return next(); // Sahibi ise devam et
            } else {
                return res.status(403).send('Yasak: Bu kaynağın sahibi siz değilsiniz.');
            }
        } catch (error) {
            console.error('isOwner middleware hatası:', error);
            res.status(500).send('Sunucu hatası.');
        }
    };
}
  

Bu isOwner middleware'ı, bir model (gerçekte veritabanı modeliniz) ve paramName (kaynak ID'sinin URL'de nerede olduğu, varsayılan id) alır. userIdField ise kaynak içindeki kullanıcı ID'sini tutan alanın adıdır. Middleware, isteğin parametrelerinden kaynak ID'sini alır, veritabanından ilgili kaynağı çeker ve req.user.id ile kaynağın sahibinin ID'sini karşılaştırır. Eğer eşleşme varsa, işlem devam eder.

2. Kaynak Tabanlı Rotları Uygulama:

Şimdi bu middleware'ı blog gönderilerini düzenleme rotasına uygulayalım. Unutmayın, bu rotaya erişmek için önce kullanıcının giriş yapmış olması (isAuthorized) ve belki de "editor" rolünde olması (hasRole('editor')) gerekebilir.


// ... (Önceki Passport.js ve RBAC middleware kodları) ...

// Örneğin, bir 'posts' rotası için:
app.get('/posts/:id', isAuthorized, (req, res) => {
    // Burada gönderiyi veritabanından çekip gösteririz
    res.send(Gönderi detayı: ${req.params.id});
});

// Gönderiyi güncelleme veya silme - sadece sahibi ve yöneticiler
// hasRole middleware'ı genel erişimi kısıtlarken, isOwner spesifik kaynağı korur.
app.put('/posts/:id', isAuthorized, hasRole(['admin', 'editor']), isOwner('PostModel', 'id', 'userId'), (req, res) => {
    // req.resource nesnesi artık güncellenecek gönderiyi içerir
    res.send(Gönderi ${req.resource.id} güncellendi. (Kullanıcı: ${req.user.username}));
});

app.delete('/posts/:id', isAuthorized, hasRole(['admin', 'editor']), isOwner('PostModel', 'id', 'userId'), (req, res) => {
    res.send(Gönderi ${req.resource.id} silindi. (Kullanıcı: ${req.user.username}));
});
  

Bu yapılandırmada, bir "editor" rolündeki kullanıcı /posts/post1 adresine PUT isteği gönderdiğinde, öncelikle giriş yapmış olup olmadığı kontrol edilir (isAuthorized). Ardından rolünün "admin" veya "editor" olup olmadığına bakılır (hasRole). Son olarak, isOwner middleware'ı, post1 ID'li gönderinin sahibinin o anki req.user.id ile eşleşip eşleşmediğini kontrol eder. Eğer hepsi başarılı olursa, gönderiyi güncelleme işlemi gerçekleşir.

Karmaşık Yetkilendirme Kararları İçin Politika Tabanlı Yaklaşım

Bazen yetkilendirme kararları birden fazla koşula bağlı olabilir (örneğin, "gönderi taslak halindeyse sadece sahibi ve yöneticiler görebilir, yayınlanmışsa herkes görebilir"). Bu gibi durumlarda, daha esnek "politika tabanlı" bir yaklaşım düşünebiliriz. Her bir yetkilendirme kuralını ayrı bir politika fonksiyonu olarak tanımlarız ve bu politikaları middleware zincirinde çağırırız. Örneğin:


// Bir gönderiyi görüntüleme politikası
const canViewPost = (req, res, next) => {
    const post = req.resource; // isOwner veya başka bir middleware'dan gelen kaynak
    if (post.status === 'published' || req.user.role === 'admin' || (req.user.id === post.userId && post.status === 'draft')) {
        return next();
    }
    res.status(403).send('Yasak: Bu gönderiyi görüntüleme yetkiniz yok.');
};

// ...
app.get('/posts/:id', isAuthorized, isOwner('PostModel', 'id', 'userId'), canViewPost, (req, res) => {
    // Gönderiyi göster
});
  

Bu yaklaşım, yetkilendirme mantığını daha modüler ve okunabilir hale getirir. Her bir politika, belirli bir kararı vermek için gerekli tüm bağlamsal bilgiyi (req.user, req.resource, vb.) kullanabilir. Kaynak tabanlı ve politika tabanlı yetkilendirme, uygulamanızın güvenlik katmanını çok daha granüler ve esnek hale getirerek, kullanıcılarınıza sadece hak ettikleri erişimi sağlamanıza olanak tanır. Bu, hem güvenlik açıklarını azaltır hem de kullanıcı deneyimini iyileştirir.

Güvenlik ve Performans İpuçları: Uygulamanızı Nasıl Sağlamlaştırırsınız?

Yetkilendirme mekanizmalarını doğru bir şekilde kurmak kadar, uygulamanın genel güvenliğini ve performansını optimize etmek de büyük önem taşır. Passport.js ve Express.js ile çalışırken dikkat etmeniz gereken bazı önemli noktalar bulunmaktadır. Bu bölümde, uygulamanızı daha güvenli ve verimli hale getirecek pratik ipuçlarına değineceğiz.

1. Oturum Yönetimi ve Güvenliği

express-session ile çalışırken oturum güvenliğini artırmak için bazı adımlar atılmalıdır:

  • Güçlü Gizli Anahtar (secret): Oturum verilerini şifrelemek için kullanılan secret anahtarı, tahmin edilmesi zor, uzun ve karmaşık olmalıdır. Üretim ortamlarında bu anahtarı ortam değişkenlerinden (environment variables) alın.
  • Çerez Güvenliği: Oturum çerezlerinizin güvenliğini artırmak için aşağıdaki seçenekleri kullanın:
    • secure: true: Çerezin sadece HTTPS üzerinden gönderilmesini sağlar. Üretim ortamları için şarttır.
    • httpOnly: true: Çerezin istemci tarafı JavaScript tarafından erişilmesini engeller, XSS saldırılarına karşı koruma sağlar.
    • sameSite: 'Lax' veya 'Strict': CSRF saldırılarına karşı koruma sağlar. 'Lax' çoğu kullanım durumu için iyi bir denge sunar.
  • Oturum Süresi (maxAge): Oturumun ne kadar süreyle aktif kalacağını belirleyin. Çok uzun süreler güvenlik riski oluşturabilir. Kullanıcının tekrar giriş yapmasını gerektiren, makul bir süre belirleyin.

app.use(session({
    secret: process.env.SESSION_SECRET || 'varsayilan-cok-gizli-anahtar',
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: process.env.NODE_ENV === 'production', // Üretimde HTTPS gerektirir
        httpOnly: true,
        sameSite: 'Lax',
        maxAge: 1000 * 60 * 60 * 2 // 2 saat
    }
}));
  

2. Parola Yönetimi

Parolaları asla düz metin olarak saklamayın. Daima güçlü bir hash algoritması (örneğin bcrypt) kullanarak hash'leyin ve tuzlama (salting) kullanın. Parola karşılaştırmalarını asenkron olarak yapın.

3. Girdi Doğrulama ve Dezenfeksiyon

Kullanıcıdan gelen her türlü girdiyi (form verileri, URL parametreleri, sorgu dizeleri) sunucu tarafında mutlaka doğrulayın ve dezenfekte edin. Bu, SQL enjeksiyonu, XSS (Cross-Site Scripting) gibi yaygın saldırıları önler. express-validator gibi kütüphaneler bu konuda size yardımcı olabilir.

4. Rate Limiting

Belirli rotalara (özellikle giriş, kayıt, parola sıfırlama) yapılan isteklerin sayısını sınırlayın. Bu, kaba kuvvet (brute-force) saldırılarını ve DDoS girişimlerini yavaşlatmaya yardımcı olur. express-rate-limit popüler bir seçenektir.


const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 dakika
    max: 5, // Her IP adresinden 15 dakika içinde maksimum 5 deneme
    message: 'Çok fazla giriş denemesi yaptınız, lütfen 15 dakika sonra tekrar deneyin.'
});

app.post('/login', loginLimiter, passport.authenticate('local', { /* ... */ }));
  

5. JWT (JSON Web Tokens) Kullanımı

Eğer stateless (durumsuz) bir API geliştiriyorsanız veya mobil uygulamalarınız varsa, Passport.js'in JWT stratejilerini kullanarak oturum yerine belirteç tabanlı kimlik doğrulama yapabilirsiniz. JWT'ler, sunucuda oturum saklama ihtiyacını ortadan kaldırır ve ölçeklenebilirliği artırır. Ancak, JWT'lerin de kendi güvenlik zorlukları vardır (örneğin, belirteçlerin çalınması, yenileme belirteçlerinin yönetimi).

6. Hata Yönetimi

Hata mesajlarınızı genel ve bilgilendirici olmayan bir şekilde düzenleyin. Güvenlik açığı bilgilerini veya sunucu iç detaylarını asla hata mesajlarında doğrudan göstermeyin. Örneğin, "Kullanıcı adı veya parola yanlış" yerine sadece "Giriş başarısız" demek daha güvenlidir.

7. Güvenlik Başlıkları

helmet gibi bir kütüphane kullanarak HTTP güvenlik başlıklarını otomatik olarak ayarlayın. Bu başlıklar, uygulamanızı XSS, tıklama dolandırıcılığı (clickjacking) gibi saldırılara karşı korur.


const helmet = require('helmet');
app.use(helmet());
  

8. Performans İpuçları

  • Veritabanı Sorgu Optimizasyonu: Yetkilendirme middleware'ları genellikle veritabanı sorguları içerir (örneğin, kullanıcı rolünü almak, kaynak sahibini bulmak). Bu sorguların optimize edildiğinden ve indekslendiğinden emin olun.
  • Önbellekleme: Sıkça erişilen ancak nadiren değişen yetkilendirme verilerini (örneğin, rollerin izinleri) önbelleğe almayı düşünebilirsiniz.
  • Asenkron İşlemler: Tüm I/O işlemleri (veritabanı, dosya sistemi) asenkron olarak yapılmalıdır ki Node.js'in non-blocking yapısından tam olarak faydalanılsın.

Mobil Uyumlu HTML ve Çapraz Platform Uygulamalar

Modern uygulamalar sadece web tarayıcılarında değil, mobil cihazlarda ve farklı ekran boyutlarında da sorunsuz çalışmalıdır. Yetkilendirme mantığı genellikle backend'de yürütülse de, frontend'de kullanıcı deneyimini etkiler. Örneğin, bir kullanıcının rolüne göre menü öğelerini veya butonları dinamik olarak göstermek/gizlemek gibi. Bu nedenle, kullanıcı arayüzünüzün rol ve yetki değişikliklerine duyarlı olması önemlidir. Mobil uyumluluk için CSS media query'ler kullanmak, farklı cihazlara göre arayüzü adapte etmenin temelidir:


/* Genel stil */
.admin-button {
    display: none; /* Varsayılan olarak gizle */
}

/* Masaüstü ve tabletler için (örneğin 768px ve üzeri) */
@media screen and (min-width: 768px) {
    .admin-button.show-for-admin {
        display: block; /* Yöneticiler için görünür yap */
    }
}

/* Mobil cihazlar için (örneğin 767px ve altı) */
@media screen and (max-width: 767px) {
    .admin-button.show-for-admin {
        display: inline-block; /* Mobilde farklı bir şekilde göster */
        font-size: 0.8em;
        padding: 5px 10px;
    }
}
  

Bu örnek, admin-button sınıfına sahip bir butonun, yöneticiler için farklı ekran boyutlarında nasıl görünebileceğini gösteriyor. Frontend'de, kullanıcının rolünü kontrol eden bir JavaScript kodu veya templating engine'i ile bu show-for-admin sınıfını dinamik olarak ekleyebiliriz.

Bu ipuçları, uygulamanızın sadece fonksiyonel değil, aynı zamanda güvenli ve performanslı olmasını sağlamak için bir yol haritası sunar. Yetkilendirme, bu büyük resmin önemli bir parçasıdır ve doğru bir şekilde uygulandığında uygulamanızın bütünlüğünü korumanıza yardımcı olur.

Sonuç ve Sıkça Sorulan Sorular

Bu makalede, modern web uygulamalarının temel bir güvenlik bileşeni olan yetkilendirme kavramını derinlemesine inceledik ve özellikle Passport.js kütüphanesini kullanarak Node.js/Express.js ortamında nasıl etkili bir şekilde uygulayacağımızı adım adım ele aldık. Kimlik doğrulama ile yetkilendirme arasındaki farkı netleştirdikten sonra, Passport.js ile temel kimlik doğrulama kurulumunu öğrendik. Ardından, en yaygın yetkilendirme modellerinden biri olan Rol Tabanlı Erişim Kontrolü'nü (RBAC) özel middleware'lar aracılığıyla nasıl uygulayacağımızı ve hatta daha granüler kontrol gerektiren kaynak tabanlı yetkilendirme senaryolarını nasıl yöneteceğimizi gördük. Son olarak, uygulamanızın genel güvenliğini ve performansını artırmak için oturum yönetimi, parola güvenliği, girdi doğrulama, rate limiting ve JWT kullanımı gibi kritik ipuçlarını paylaştık.

Unutulmamalıdır ki güvenlik, sürekli bir süreçtir ve hiçbir zaman tek seferlik bir kurulumla tamamlanmaz. Uygulamalarınızı geliştirirken bu rehberde sunulan prensipleri ve en iyi uygulamaları takip etmek, olası güvenlik açıklarını en aza indirmeye ve kullanıcılarınız için güvenli bir deneyim sağlamaya yardımcı olacaktır. Passport.js, kimlik doğrulama ve yetkilendirme katmanlarını inşa etmek için sağlam ve esnek bir temel sunar; bu temeli doğru bir şekilde kullanarak uygulamalarınızı geleceğe hazır hale getirebilirsiniz.

Sıkça Sorulan Sorular

Soru Cevap
Passport.js sadece kimlik doğrulama için mi kullanılır? Hayır, Passport.js temelde bir kimlik doğrulama kütüphanesi olsa da, kimliği doğrulanan kullanıcının bilgilerini (genellikle req.user nesnesinde) Express.js middleware zincirine aktarır. Bu bilgiler, yetkilendirme middleware'ları tarafından kullanılarak kullanıcının belirli bir kaynağa veya işleme erişim izni olup olmadığına karar verilebilir. Dolayısıyla, yetkilendirme mantığını entegre etmek için güçlü bir temel sunar.
RBAC ve Kaynak Tabanlı Yetkilendirme arasındaki temel fark nedir? RBAC (Rol Tabanlı Erişim Kontrolü), kullanıcılara "rol" atanır ve bu rollere izinler verilir (örn. "admin" her şeyi yapabilir, "editor" içerik düzenleyebilir). Kaynak Tabanlı Yetkilendirme (veya ABAC), izinlerin sadece role göre değil, aynı zamanda erişilmeye çalışılan kaynağın özelliklerine veya kaynağı kimin oluşturduğuna göre verilmesidir (örn. "editör sadece kendi gönderilerini düzenleyebilir"). Kaynak tabanlı yetkilendirme daha granüler kontrol sağlar.
Passport.js ile JWT tabanlı yetkilendirme nasıl yapılır? Passport.js, passport-jwt gibi özel stratejilerle JWT (JSON Web Token) tabanlı kimlik doğrulamayı destekler. Bu stratejide, kullanıcı giriş yaptığında bir JWT oluşturulur ve istemciye gönderilir. Sonraki her istekte istemci bu JWT'yi gönderir ve passport-jwt middleware'ı belirteci doğrulayarak kullanıcının kimliğini onaylar. Yetkilendirme kararları yine req.user nesnesine eklenen JWT payload'ındaki bilgilere (örn. rol) dayanır.
Yetkilendirme middleware'larımın performansını nasıl optimize edebilirim? Performans optimizasyonu için birkaç yöntem vardır: Veritabanı sorgularını optimize edin ve indeksler kullanın. Sıkça erişilen ancak nadiren değişen yetkilendirme verilerini (örn. rollerin izinleri) önbelleğe alın. Middleware zincirinde gereksiz sorguları veya karmaşık hesaplamaları erken kesmeye çalışın. Ayrıca, tüm I/O işlemlerini asenkron olarak yürüttüğünüzden emin olun.
Sadece yetkilendirme middleware'ı kullanmak yeterli mi? Hayır, tek başına yetkilendirme middleware'ı yeterli değildir. Güvenlik, çok katmanlı bir yaklaşımla sağlanmalıdır. Parola hash'leme, oturum yönetimi, girdi doğrulama, rate limiting, güvenlik başlıkları (Helmet) gibi diğer güvenlik pratiklerini de uygulamanız kritik öneme sahiptir. Yetkilendirme, bu güvenlik katmanlarından sadece biridir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.