Takip et

Express.js ve Node.js’te JWT Kimlik Doğrulama: Kapsamlı Rehber

JSON Web Token (JWT), taraflar arasında güvenli bir şekilde bilgi alışverişini sağlayan kompakt, URL güvenli bir yöntemdir. Özellikle API tabanlı …

Express.js ve Node.js’te JWT Kimlik Doğrulama: Kapsamlı Rehber

Modern web uygulamalarında kullanıcı kimlik doğrulama ve yetkilendirme süreçleri, güvenliğin temelini oluşturur. Bu bağlamda JSON Web Token (JWT), sunucu tarafında oturum bilgisi tutma ihtiyacını ortadan kaldıran, hafif ve esnek bir yöntem olarak öne çıkar. Bu makalede, Node.js ve Express.js kullanarak JWT tabanlı kimlik doğrulamanın nasıl adım adım uygulanacağını, temel kavramlardan en iyi güvenlik uygulamalarına kadar tüm detaylarıyla ele alacağız.

JWT Nedir ve Neden Kullanılır?

JSON Web Token (JWT), taraflar arasında güvenli bir şekilde bilgi alışverişini sağlayan kompakt, URL güvenli bir yöntemdir. Özellikle API tabanlı uygulamalarda, mobil ve tek sayfa uygulamalarında (SPA) kullanıcı kimlik doğrulama ve yetkilendirme için sıklıkla tercih edilir. JWT’nin temel amacı, bir kullanıcının kimliğini doğruladıktan sonra, sunucunun bu kullanıcıya belirli kaynaklara erişim yetkisi vermesini sağlamaktır.

Kimlik Doğrulama ve Yetkilendirme Farkı

  • Kimlik Doğrulama (Authentication): Kullanıcının kim olduğunu kanıtlama sürecidir. Örneğin, kullanıcı adı ve şifre ile giriş yapma.
  • Yetkilendirme (Authorization): Kimliği doğrulanmış bir kullanıcının belirli bir kaynağa veya eyleme erişim izni olup olmadığını belirleme sürecidir. Örneğin, bir yöneticinin diğer kullanıcıların profillerini düzenleyebilmesi.

JWT, hem kimlik doğrulama sonrasında kullanıcıya bir token vererek kimliğini kanıtlamasına yardımcı olur hem de bu token içindeki bilgilerle yetkilendirme kararları alınmasını kolaylaştırır.

Geleneksel Oturum Yönetimine Alternatif

Geleneksel oturum yönetiminde, kullanıcı giriş yaptığında sunucu tarafında bir oturum kimliği (session ID) oluşturulur ve bu kimlik bir çerez (cookie) aracılığıyla istemciye gönderilir. Her istekte istemci bu çerezi sunucuya geri gönderir ve sunucu, oturum kimliğini veritabanında veya bellekte tuttuğu oturum bilgileriyle karşılaştırır. JWT ise bu duruma bir alternatif sunar.

Durumsuz (Stateless) Yapının Avantajları

JWT’nin en büyük avantajlarından biri, sunucu tarafında oturum durumu (state) tutma ihtiyacını ortadan kaldırmasıdır. Her JWT, kullanıcının kimliği ve yetkileri hakkında gerekli tüm bilgiyi kendi içinde barındırır. Bu durum, özellikle mikroservis mimarilerinde ve yatay ölçeklenebilirlik (horizontal scaling) gerektiren uygulamalarda büyük avantaj sağlar:

  • Ölçeklenebilirlik: Her sunucu token’ı bağımsız olarak doğrulayabilir, bu da yük dengeleyicilerle birden fazla sunucu arasında kolayca dağıtım yapılmasına olanak tanır.
  • Basitlik: Sunucu tarafında oturum veritabanı yönetimi veya çerez depolama gibi karmaşık işlemleri azaltır.
  • Çapraz Alan Erişimi (CORS): JWT’ler HTTP başlıklarında taşındığı için, farklı alan adları arasında kimlik doğrulama yapmak daha kolaydır.

JWT’nin Faydaları

  • Güvenlik: İmza sayesinde token’ın içeriğinin değiştirilmediği garanti edilir.
  • Hafiflik: JSON formatında olduğu için kompakt ve kolayca iletilir.
  • Esneklik: Herhangi bir programlama dili veya platformda kullanılabilir.
  • Standartlaşma: RFC 7519 ile tanımlanmış açık bir standarttır.

JWT Yapısı ve Bileşenleri

Bir JWT, üç ana bölümden oluşur ve bu bölümler noktalarla (.) ayrılır:


header.payload.signature

Her bölüm Base64Url ile kodlanmıştır.

Header (Başlık)

Header, token'ın türünü (JWT) ve kullanılan imzalama algoritmasını (örneğin, HS256 veya RS256) belirtir. Örnek bir başlık:


{
  "alg": "HS256",
  "typ": "JWT"
}

Bu JSON nesnesi Base64Url ile kodlanır ve JWT'nin ilk kısmını oluşturur.

Payload (Yük)

Payload, token'ın asıl verilerini (claim'leri) içerir. Claim'ler, kullanıcı hakkında bilgiler (örneğin, kullanıcı ID'si, adı, rolleri) veya token'ın geçerlilik süresi gibi meta veriler olabilir. Üç tür claim vardır:

  • Kayıtlı Claim'ler (Registered Claims): JWT standartları tarafından tanımlanmış, ancak zorunlu olmayan claim'lerdir. Örnekler: iss (issuer), exp (expiration time), sub (subject), aud (audience).
  • Genel Claim'ler (Public Claims): Çakışmaları önlemek için IANA JWT Kayıt Defteri'nde tanımlanmış veya kendi ad alanınızda tanımlanmış özel claim'lerdir.
  • Özel Claim'ler (Private Claims): Uygulamanızın ihtiyaçlarına göre tanımladığınız özel claim'lerdir. Örneğin, userId veya userRole.

Örnek bir payload:


{
  "userId": "12345",
  "username": "exampleuser",
  "role": "admin",
  "iat": 1678886400, // Issued At (token oluşturulma zamanı)
  "exp": 1678890000  // Expiration Time (token bitiş zamanı)
}

Bu JSON nesnesi de Base64Url ile kodlanır ve JWT'nin ikinci kısmını oluşturur.

Signature (İmza)

İmza, JWT'nin bütünlüğünü ve orijinalliğini doğrulamak için kullanılır. Header ve Payload'ın Base64Url kodlanmış halleri, bir gizli anahtar (secret key) kullanılarak Header'da belirtilen algoritma ile şifrelenir. İmza şu şekilde oluşturulur:


HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret_key
)

İmza, token'ın değiştirilmediğini garanti eder. Eğer header veya payload değiştirilirse, imza geçersiz olur ve token reddedilir.

Örnek Bir JWT

Yukarıdaki header ve payload kullanılarak oluşturulmuş bir JWT şu şekilde görünebilir:


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsInVzZXJuYW1lIjoiZXhhbXBsZXVzZXIiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2Nzg4ODY0MDAsImV4cCI6MTY3ODg5MDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Express.js Projesi Kurulumu

JWT kimlik doğrulamasını uygulamak için öncelikle temel bir Express.js projesi kurmamız gerekiyor.

Gerekli Paketlerin Kurulumu

Proje dizininizde aşağıdaki komutları çalıştırarak gerekli paketleri kurun:


mkdir jwt-auth-app
cd jwt-auth-app
npm init -y
npm install express jsonwebtoken dotenv bcryptjs

  • express: Web sunucumuzu oluşturmak için.
  • jsonwebtoken: JWT oluşturma ve doğrulama için.
  • dotenv: Ortam değişkenlerini yönetmek için.
  • bcryptjs: Şifreleri güvenli bir şekilde hashlemek için.

Temel Express Uygulaması

Proje kök dizininde app.js adında bir dosya oluşturun ve aşağıdaki temel Express uygulamasını ekleyin:


// app.js
require('dotenv').config(); // Ortam değişkenlerini yükle

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

app.use(express.json()); // JSON body'leri parse etmek için

app.get('/', (req, res) => {
    res.send('JWT Kimlik Doğrulama Uygulamasına Hoş Geldiniz!');
});

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

Ortam Değişkenlerinin Yönetimi (.env)

Güvenlik anahtarları gibi hassas bilgileri kod içinde tutmak yerine ortam değişkenleri olarak saklamak en iyi uygulamadır. Proje kök dizininde .env adında bir dosya oluşturun:


// .env
JWT_SECRET=cok_gizli_anahtariniz_burada_olmali
PORT=5000

JWT_SECRET değerini güçlü ve tahmin edilemez bir dizeyle değiştirin.

JWT Kimlik Doğrulama Akışı

JWT tabanlı kimlik doğrulama genellikle aşağıdaki adımları içerir:

  1. Kullanıcı Kaydı (Register): Kullanıcı, kullanıcı adı ve şifre gibi bilgilerle kayıt olur. Şifre hashlenerek veritabanına kaydedilir.
  2. Kullanıcı Girişi (Login): Kullanıcı, kullanıcı adı ve şifresiyle giriş yapmaya çalışır.
  3. Token Oluşturma: Sunucu, kullanıcının kimliğini doğruladıktan sonra, kullanıcı bilgilerini içeren bir JWT oluşturur ve imzalar.
  4. Token Gönderme: Sunucu, oluşturulan JWT'yi istemciye yanıt olarak gönderir (genellikle HTTP başlığında veya yanıt gövdesinde).
  5. Token Depolama: İstemci (tarayıcı, mobil uygulama), token'ı güvenli bir şekilde saklar (örneğin, localStorage, sessionStorage veya HTTP Only çerezleri).
  6. Korunan Rotalara Erişim: İstemci, korunan bir rotaya erişmek istediğinde, sakladığı JWT'yi her istekle birlikte HTTP Authorization başlığında (Bearer Token formatında) sunucuya gönderir.
  7. Token Doğrulama: Sunucu, gelen JWT'yi doğrular (imzasını kontrol eder, süresinin dolup dolmadığını kontrol eder).
  8. Yetkilendirme: Token geçerliyse, sunucu token içindeki bilgilere göre kullanıcının istenen kaynağa erişim yetkisi olup olmadığını kontrol eder.
  9. Yanıt: Sunucu, yetkilendirme durumuna göre isteği işler ve yanıt döner.

JWT Oluşturma ve İmzalama

Şimdi bir kullanıcı kayıt ve giriş endpoint'i oluşturalım ve başarılı girişte JWT üretelim.

Kullanıcı Kaydı (Register) Endpoint'i

app.js dosyanıza aşağıdaki kodu ekleyin. Basitlik adına kullanıcıları bellekte tutacağız, ancak gerçek bir uygulamada veritabanı kullanmalısınız.


// app.js (devamı)
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

// Geçici kullanıcı veritabanı
const users = []; // Gerçek uygulamada MongoDB, PostgreSQL vb. kullanılmalı

app.post('/register', async (req, res) => {
    const { username, password } = req.body;

    if (!username || !password) {
        return res.status(400).json({ message: 'Kullanıcı adı ve şifre gerekli.' });
    }

    // Kullanıcı zaten var mı kontrol et
    if (users.find(u => u.username === username)) {
        return res.status(409).json({ message: 'Bu kullanıcı adı zaten mevcut.' });
    }

    try {
        const hashedPassword = await bcrypt.hash(password, 10);
        const newUser = { id: users.length + 1, username, password: hashedPassword };
        users.push(newUser);
        res.status(201).json({ message: 'Kullanıcı başarıyla kaydedildi.', user: { id: newUser.id, username: newUser.username } });
    } catch (error) {
        res.status(500).json({ message: 'Kayıt sırasında bir hata oluştu.', error: error.message });
    }
});

Kullanıcı Girişi (Login) Endpoint'i

Giriş başarılı olduğunda, jsonwebtoken.sign() metodunu kullanarak bir JWT oluşturacağız.


// app.js (devamı)
app.post('/login', async (req, res) => {
    const { username, password } = req.body;

    if (!username || !password) {
        return res.status(400).json({ message: 'Kullanıcı adı ve şifre gerekli.' });
    }

    const user = users.find(u => u.username === username);
    if (!user) {
        return res.status(400).json({ message: 'Kullanıcı adı veya şifre hatalı.' });
    }

    try {
        const isMatch = await bcrypt.compare(password, user.password);
        if (!isMatch) {
            return res.status(400).json({ message: 'Kullanıcı adı veya şifre hatalı.' });
        }

        // JWT oluştur
        const token = jwt.sign(
            { userId: user.id, username: user.username, role: 'user' }, // Payload
            process.env.JWT_SECRET, // Gizli anahtar
            { expiresIn: '1h' } // Token süresi
        );

        res.status(200).json({ message: 'Giriş başarılı.', token });

    } catch (error) {
        res.status(500).json({ message: 'Giriş sırasında bir hata oluştu.', error: error.message });
    }
});

jsonwebtoken Kütüphanesinin Kullanımı

Yukarıdaki örnekte jwt.sign() metodunu kullandık:

  • İlk argüman: Token'ın payload'ı olacak JavaScript nesnesi.
  • İkinci argüman: Token'ı imzalamak için kullanılacak gizli anahtar (JWT_SECRET).
  • Üçüncü argüman (isteğe bağlı): Token'ın seçenekleri, örneğin expiresIn (token'ın ne kadar süre geçerli olacağı).

JWT Doğrulama ve Koruma

Bir kez token oluşturulduğunda, istemci bu token'ı korunan rotalara erişmek için kullanacaktır. Sunucu tarafında ise bu token'ın geçerliliğini doğrulamamız gerekir.

Middleware Oluşturma

Express.js'te middleware'ler, isteklere yanıt vermeden önce veya sonra çalıştırılan fonksiyonlardır. JWT doğrulaması için bir middleware oluşturacağız.

middleware/auth.js adında bir dosya oluşturun:


// middleware/auth.js
const jwt = require('jsonwebtoken');

const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN_STRING

    if (token == null) {
        return res.status(401).json({ message: 'Erişim reddedildi. Token bulunamadı.' });
    }

    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
        if (err) {
            // Token geçersiz veya süresi dolmuş
            return res.status(403).json({ message: 'Geçersiz veya süresi dolmuş token.' });
        }
        req.user = user; // Doğrulanmış kullanıcı bilgilerini isteğe ekle
        next(); // Bir sonraki middleware'e veya route handler'a geç
    });
};

module.exports = authenticateToken;

Token'ı Header'dan Çekme

Yukarıdaki middleware'de, token'ı HTTP Authorization başlığından çekiyoruz. İstemciler token'ı genellikle Bearer formatında gönderirler.


const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN_STRING" -> ["Bearer", "TOKEN_STRING"]

Token'ı Doğrulama

jwt.verify() metodu, token'ı ve gizli anahtarı kullanarak token'ın geçerliliğini kontrol eder. Eğer token geçerliyse, payload'ı içeren bir user nesnesi döner. Geçersizse veya süresi dolmuşsa bir hata fırlatır.


jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    // ...
});

Korunan Rotalara Uygulama

Şimdi bu middleware'i korumak istediğimiz rotalara uygulayalım. app.js dosyanıza aşağıdaki kodu ekleyin:


// app.js (devamı)
const authenticateToken = require('./middleware/auth');

app.get('/protected', authenticateToken, (req, res) => {
    res.json({
        message: 'Korunan rotaya hoş geldiniz!',
        user: req.user // Middleware'den gelen kullanıcı bilgileri
    });
});

app.get('/admin', authenticateToken, (req, res) => {
    if (req.user.role !== 'admin') {
        return res.status(403).json({ message: 'Bu rotaya erişim yetkiniz yok.' });
    }
    res.json({ message: 'Admin paneline hoş geldiniz!', user: req.user });
});

Artık /protected rotasına erişmek için geçerli bir JWT'ye ihtiyacınız var. /admin rotası ise hem geçerli bir JWT hem de token içindeki role değerinin 'admin' olmasını gerektirir.

Güvenlik En İyi Uygulamaları ve Dikkat Edilmesi Gerekenler

JWT kullanırken güvenliği artırmak için bazı önemli noktalara dikkat etmek gerekir.

Secret Key Güvenliği

JWT_SECRET kesinlikle gizli tutulmalıdır. Asla versiyon kontrol sistemlerine (Git) yüklenmemeli ve güçlü, rastgele bir dize olmalıdır. Ortam değişkenleri veya özel bir anahtar yönetim sistemi kullanın.

Token Süresi ve Yenileme

expiresIn değeri çok uzun olmamalıdır (örneğin, 15 dakika - 1 saat arası). Kısa süreli token'lar, çalınmaları durumunda oluşturabilecekleri zararı sınırlar. Uzun süreli oturumlar için token yenileme (refresh token) mekanizması kullanılabilir. Refresh token'lar genellikle daha uzun ömürlüdür ve yeni bir access token almak için kullanılırlar. Refresh token'lar veritabanında saklanmalı ve tek kullanımlık olmalıdır.

Token Kara Listeleme (Blacklisting)

JWT'ler doğaları gereği durumsuz olduklarından, bir token'ı iptal etmek (örneğin, kullanıcı çıkış yaptığında veya bir güvenlik ihlali durumunda) zordur. Token'ın süresi dolana kadar geçerli kalır. Bu sorunu çözmek için, iptal edilen token'ları bir kara listeye (veritabanı, Redis gibi hızlı bir önbellek) kaydedebilirsiniz. Her istekte, gelen token'ın kara listede olup olmadığını kontrol edersiniz.

HTTPS Kullanımı

Tüm iletişim kanalları (istemci-sunucu arası) HTTPS ile şifrelenmelidir. Aksi takdirde, JWT'ler ağ üzerinden düz metin olarak iletilir ve dinleme saldırılarına (eavesdropping) açık hale gelir.

XSS ve CSRF Koruması

  • XSS (Cross-Site Scripting): JWT'leri localStorage'da saklamak XSS saldırılarına karşı savunmasız hale getirebilir, çünkü kötü amaçlı komut dosyaları token'a erişebilir. HTTP Only çerezleri bu riski azaltır çünkü JavaScript bu çerezlere erişemez.
  • CSRF (Cross-Site Request Forgery): HTTP Only çerezleri CSRF'ye karşı savunmasız olabilir. CSRF koruması için genellikle anti-CSRF token'lar veya SameSite çerez niteliği kullanılır.

Payload'da Hassas Bilgi Saklamaktan Kaçının

JWT payload'ı Base64Url ile kodlandığı için herkes tarafından okunabilir. Bu nedenle, şifreler, kredi kartı numaraları gibi hassas bilgileri payload içinde saklamayın. Sadece kullanıcı kimliği ve yetkilendirme için gerekli minimum bilgiyi ekleyin.

Sonuç

JWT kimlik doğrulama, modern web uygulamaları için güçlü, esnek ve ölçeklenebilir bir çözümdür. Node.js ve Express.js ile entegrasyonu oldukça basittir ve geliştiricilere stateless API'ler oluşturma imkanı sunar. Ancak, güvenlik en iyi uygulamalarına dikkat etmek, özellikle secret key'in korunması, token sürelerinin yönetimi ve uygun depolama stratejilerinin kullanılması kritik öneme sahiptir. Bu rehber sayesinde, JWT tabanlı kimlik doğrulama sistemlerini güvenli ve etkili bir şekilde uygulayabilecek bilgiye sahip olduğunuzu umuyoruz.

SSS (Sık Sorulan Sorular)

JWT ile oturum tabanlı kimlik doğrulama arasındaki temel fark nedir?

JWT, sunucu tarafında oturum durumu (state) tutmaz; her token kendi içinde gerekli tüm bilgiyi barındırır. Oturum tabanlı sistemler ise sunucuda oturum kimliğini ve ilgili verileri saklar. JWT'nin durumsuz yapısı, özellikle dağıtık sistemlerde ve ölçeklenebilirlik açısından avantaj sağlar.

JWT'yi istemci tarafında nerede saklamalıyım?

Bu, bir tartışma konusudur. Seçenekler arasında localStorage, sessionStorage ve HTTP Only çerezleri bulunur. localStorage ve sessionStorage XSS saldırılarına karşı savunmasızken, HTTP Only çerezleri JavaScript erişimine kapalı olduğu için XSS riskini azaltır. Ancak HTTP Only çerezleri de CSRF'ye karşı dikkatli olunmasını gerektirir.

Token'ın süresi dolduğunda ne yapmalıyım?

Token'ın süresi dolduğunda, istemci genellikle bir hata alır (403 Forbidden). Bu durumda, istemci yeni bir access token almak için refresh token kullanabilir (eğer bir refresh token mekanizması uygulandıysa) veya kullanıcıdan tekrar giriş yapmasını isteyebilir.

JWT'ler neden şifrelenmez?

JWT'ler varsayılan olarak şifrelenmez, sadece imzalanır. İmzalama, token içeriğinin değiştirilmediğini garanti ederken, şifreleme token içeriğinin okunmasını engeller. Eğer token içindeki bilgilerin gizli kalması gerekiyorsa, JSON Web Encryption (JWE) kullanılabilir, ancak bu, JWT'nin temel kullanım durumundan daha karmaşıktır.

Bir JWT'yi nasıl iptal edebilirim (logout veya güvenlik ihlali durumunda)?

JWT'ler durumsuz olduğu için doğrudan iptal edilemezler; süresi dolana kadar geçerli kalırlar. İptal mekanizması için genellikle bir kara liste (blacklist) kullanılır. İptal edilmek istenen token'lar, süresi dolana kadar bir veritabanına veya önbelleğe kaydedilir ve her istekte token'ın kara listede olup olmadığı kontrol edilir.

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.