Yazılarımız

OfisData

NODE.JS JWT AUTHENTICATION KURMAK

Node.js yeşil hex logo solda JWT token kart üç parçaya bölünmüş header payload signature ve sarı anahtar ikonu beyaz fonda

Modern web ve mobil uygulamalarda kullanıcı kimlik doğrulamanın en yaygın yöntemi JWT (JSON Web Token). Cookie-session tabanlı eski model sunucuda state tutar; JWT stateless çalışır. Token sunucu tarafında saklanmaz; kullanıcı her istekte token gönderir, sunucu doğrular, işine bakar.

Bu yaklaşımın avantajı ölçeklenebilirlik; yüz farklı sunucu aynı token'ı doğrulayabilir, sticky session gerekmez. Dezavantajı ise iptal mekanizmasının karmaşık olması; bir token çalındıysa expire olana kadar geçerli kalır.

Aşağıda JWT'nin nasıl çalıştığını, Node.js'te kurulum adımlarını, access/refresh token stratejisini ve güvenli saklama yöntemlerini anlatıyoruz.

JWT nedir?

JSON Web Token; üç parçadan oluşan kodlanmış string:

header.payload.signature

Örnek:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0In0.aBcDeFgHiJ

Üç parça

  • Header: Algoritma ve token tipi (HS256, RS256 vs)
  • Payload: Kullanıcı bilgisi (user_id, email, role)
  • Signature: Header + payload'ın secret key ile imzalı hash'i

Header ve payload Base64-encoded; herkes okuyabilir. Signature ise secret key gerektirir; sahte token üretmek imkansız. crypto modülü ve güvenli secret üretimi gibi konularda resmi Node.js dokümantasyonu ayrıntılı örnekler sunar.

JWT vs Session

ÖzellikJWTSession
StateStateless (sunucuda saklanmaz)Stateful (sunucuda saklanır)
ÖlçeklenebilirlikÇok yüksekSticky session gerekir
RevocationKarmaşıkKolay (DB'den sil)
BoyutBüyük (~500 byte)Küçük (session ID)
Mobil uyumMükemmelZor

Node.js JWT kurulumu

jsonwebtoken paketi en yaygın:

npm install jsonwebtoken bcrypt

Token üretme (login)

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

async function login(req, res) {
  const { email, password } = req.body;

  // 1. Kullanıcıyı bul
  const user = await db.users.findOne({ email });
  if (!user) {
    return res.status(401).json({ error: 'Geçersiz bilgiler' });
  }

  // 2. Şifre kontrol
  const validPassword = await bcrypt.compare(password, user.password);
  if (!validPassword) {
    return res.status(401).json({ error: 'Geçersiz bilgiler' });
  }

  // 3. Token üret
  const token = jwt.sign(
    { userId: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '1h' }
  );

  res.json({ token });
}

Token doğrulama (middleware)

function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Token gerekli' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token süresi doldu' });
    }
    res.status(403).json({ error: 'Geçersiz token' });
  }
}

// Kullanım
app.get('/api/profile', authenticate, (req, res) => {
  res.json({ userId: req.user.userId });
});

Access ve Refresh Token stratejisi

Tek token modeli güvenlik sorunu yaratır: token uzun süreli olursa çalınma riski; kısa süreli olursa kullanıcı sürekli login. Çözüm: iki token.

  • Access token: Kısa süreli (15 dakika); API çağrılarında kullanılır
  • Refresh token: Uzun süreli (7-30 gün); yeni access token almak için kullanılır

Login response

const accessToken = jwt.sign(
  { userId: user.id },
  process.env.JWT_ACCESS_SECRET,
  { expiresIn: '15m' }
);

const refreshToken = jwt.sign(
  { userId: user.id },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: '7d' }
);

// Refresh token'ı DB'ye kaydet (revocation için)
await db.refreshTokens.insert({ token: refreshToken, userId: user.id });

res.json({ accessToken, refreshToken });

Refresh endpoint

app.post('/api/refresh', async (req, res) => {
  const { refreshToken } = req.body;

  // 1. DB'de var mı kontrol
  const stored = await db.refreshTokens.findOne({ token: refreshToken });
  if (!stored) {
    return res.status(403).json({ error: 'Geçersiz refresh token' });
  }

  // 2. Token doğrula
  try {
    const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);

    // 3. Yeni access token üret
    const newAccessToken = jwt.sign(
      { userId: decoded.userId },
      process.env.JWT_ACCESS_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  } catch (error) {
    res.status(403).json({ error: 'Refresh token süresi doldu' });
  }
});
VS Code dark theme login.js editör jsonwebtoken require ve jwt.sign userId expiresIn payload kod bloğu syntax highlight

Güvenli saklama

Frontend (browser)

Üç seçenek var:

  • localStorage: Kolay ama XSS saldırılarına açık
  • sessionStorage: Tab kapanınca silinir; XSS riski hâlâ var
  • httpOnly cookie: En güvenli; JavaScript erişemez

Modern öneri: access token in-memory (state), refresh token httpOnly secure cookie.

Cookie ile saklama

res.cookie('refreshToken', refreshToken, {
  httpOnly: true,    // JavaScript erişemez
  secure: true,      // Sadece HTTPS
  sameSite: 'strict', // CSRF koruması
  maxAge: 7 * 24 * 60 * 60 * 1000 // 7 gün
});

Token revocation (iptal)

JWT'nin en zor problemi; token üretildikten sonra expire olana kadar geçerli. İptal etmek için:

Refresh token blacklist

Logout veya güvenlik sebebiyle iptal edilen refresh token'ları DB'de "iptal" olarak işaretle. Refresh endpoint'i kontrol etsin.

app.post('/api/logout', async (req, res) => {
  const { refreshToken } = req.body;
  await db.refreshTokens.deleteOne({ token: refreshToken });
  res.json({ message: 'Çıkış yapıldı' });
});

Access token kısa süreli

Kısa süreli access token 15 dakikalık olursa iptal etme ihtiyacı pratik olarak azalır; en kötü 15 dakika sonra geçersiz.

Token blacklist (extreme durumlar için)

Acil iptal gerekiyorsa Redis'te blacklist tutulur. Her istekte token blacklist'te mi kontrol edilir.

const redis = require('redis');
const client = redis.createClient();

async function checkBlacklist(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  const isBlacklisted = await client.get(`blacklist:${token}`);
  if (isBlacklisted) {
    return res.status(403).json({ error: 'Token iptal edildi' });
  }
  next();
}

Payload tasarımı

JWT payload'a ne konulmalı?

Konulmalı

  • userId
  • role (admin, user)
  • iat (issued at, otomatik)
  • exp (expiration, otomatik)

Konulmamalı

  • Şifre (asla)
  • Kart bilgisi
  • Çok fazla kişisel veri (token boyutunu büyütür)
  • Sık değişen veri (token'da güncel olmaz)

JWT payload herkes tarafından okunabilir (Base64 decode); hassas bilgi koymak ciddi güvenlik riskidir.

HS256 vs RS256

HS256RS256
AlgoritmaSymmetric (HMAC)Asymmetric (RSA)
KeyTek secret keyPublic/private key çifti
ÜretmeSecret ilePrivate key ile
DoğrulamaAynı secret ilePublic key ile
KullanımMonolithMicroservices

Tek bir sunucu için HS256 yeterli; mikroservis mimaride asimetrik RS256 daha uygun (token üreten ve doğrulayan farklı servisler).

Yaygın güvenlik hataları

  1. Zayıf secret key. "123456" yerine cryptographic random (32+ karakter)
  2. Secret'i hardcoded yazmak. .env dosyasında tut
  3. localStorage'da token saklamak. XSS riski; httpOnly cookie tercih edilmeli
  4. HTTPS olmadan kullanmak. Token açıkta gider; ortadaki adam saldırıları
  5. Token'da hassas veri. Payload base64; herkes okur
  6. Çok uzun expiration. 1 yıllık token = 1 yıllık güvenlik açığı
  7. Refresh token rotation yapmamak. Her kullanımda yeni refresh token üret; eskisi iptal

Refresh token rotation

İleri güvenlik pratiği; her refresh işleminde hem access hem refresh token yeniden üretilir:

app.post('/api/refresh', async (req, res) => {
  const { refreshToken: oldToken } = req.body;

  // Eski token'ı doğrula
  const decoded = jwt.verify(oldToken, process.env.JWT_REFRESH_SECRET);

  // Eski token'ı invalidate et
  await db.refreshTokens.deleteOne({ token: oldToken });

  // Yeni token çifti üret
  const newAccess = jwt.sign({ userId: decoded.userId }, ACCESS_SECRET, { expiresIn: '15m' });
  const newRefresh = jwt.sign({ userId: decoded.userId }, REFRESH_SECRET, { expiresIn: '7d' });

  await db.refreshTokens.insert({ token: newRefresh, userId: decoded.userId });

  res.json({ accessToken: newAccess, refreshToken: newRefresh });
});

Çalınmış token tespit edildiğinde tüm zincir invalidate edilebilir.

Terminal pencere dark theme curl POST login isteği Authorization Bearer token başlığı ve 200 OK JSON response çıktısı

Sürdürme Yolu

JWT authentication modern API geliştirmenin temelidir. Daha kapsamlı bir pratik için kapsamlı Node.js eğitimi authentication ve güvenli API tasarımı konularını birlikte aktarır.

Yapının Özeti

JWT authentication stateless token tabanlı modern kimlik doğrulama. jsonwebtoken paketi ile kolay kurulum; access (kısa) + refresh (uzun) token stratejisi standart. Frontend'de httpOnly cookie tercih edilir; localStorage XSS risklidir. Refresh token rotation ile çalınmış token tespiti mümkün. Payload'a hassas veri konulmaz; secret key güçlü ve .env dosyasında. HTTPS olmadan asla kullanılmaz.

 Vimaj