📝 Basit Anlatım (Sorun Nedir?)
Kullanıcı muzibu.com'de TEK CİHAZDAN müzik dinlerken, sistemden "Başka bir cihazdan giriş yapıldı. Bu oturum sonlandırıldı." uyarısı alıyor ve oturumu kapanıyor.
UYARI YANLIŞ! Gerçekte başka cihazdan giriş yapılmamış. Sistem kullanıcının kendi oturumunu yanlışlıkla "saldırgan oturum" olarak algılayıp sonlandırıyor.
Kullanıcı deneyimi çok kötü! Müzik dinlerken sürekli atılmak → Platformdan soğuma → Rakip platformlara geçiş (Spotify, Apple Music).
Sistem Nasıl Çalışıyor? (Concurrent Session Management)
🔄 LIFO Mekanizması (Last In, First Out)
Sistem, aynı kullanıcının birden fazla cihazdan giriş yapmasını engellemek için LIFO (Son Giren İlk, Öncekiler Çıkar) yaklaşımı kullanıyor.
🔑 Login Token Cookie Mekanizması
Livewire her sayfa geçişinde session()->getId()
değiştirebiliyor (session regeneration). Bu yüzden sistem login token cookie kullanıyor.
- • Cookie:
mzb_login_token - • Süre: 7 gün
- • Session ID değişse bile token sabit
- • DB kontrolü:
login_tokeneşleşmesi
- • Livewire regenerate → ID değişir
- • DB'de eski ID aranır
- • Bulunamaz → False positive logout
- • Kullanıcı atılır
📡 Frontend Polling Sistemi
Frontend her 5 saniyede bir backend'e istek atarak oturumun geçerliliğini kontrol ediyor.
const SESSION_POLL_INTERVAL = 5000; // 🧪 TEST: 5 saniye
setInterval(() => {
this.checkSessionValidity(); // → /api/auth/check-session
}, SESSION_POLL_INTERVAL);
300000ms (5 dakika) kullanılmalı.
🔍 Muhtemel Nedenler (6 Senaryo)
🍪 Login Token Cookie Silinmesi/Kaybolması
mzb_login_token cookie'si
browser tarafından silinmiş veya SameSite/Secure politikaları nedeniyle gönderilmemiş olabilir.
cookie($cookieName, $loginToken, $cookieMinutes, '/', null,
true, // secure (HTTPS zorunlu)
true, // httpOnly (JS erişemez)
false, // raw
'Lax' // SameSite=Lax
);
- Browser gizli mod → Cookie silinir
- Cookie temizleme eklentisi (Privacy Badger, uBlock vb.)
- Cross-site isteklerde SameSite=Lax bloğu (iframe, 3rd party embed)
- HTTPS olmayan sayfadan istek (secure=true gereksinimi)
⚡ Race Condition: registerSession vs checkSession
Login sonrası registerSession() DB'ye kayıt yaparken, polling sistemi aynı anda checkSession() çağırıyor.
registerSession()// 🔧 LOGIN SONRASI: Session DB'ye kaydedilmesi için 2 saniye bekle
setTimeout(() => {
this.checkSessionValidity();
}, 2000); // Yeterli mi? Yavaş network'te değil!
🔄 Livewire Session ID Regeneration
Livewire bazı sayfa geçişlerinde session()->regenerate()
çağırıyor. Bu durumda session ID değişiyor ama login_token aynı kalıyor.
if ($session) {
// Token eşleşti - session geçerli
// Session ID değiştiyse güncelle (Livewire regenerate)
if ($currentSessionId && $session->session_id !== $currentSessionId) {
DB::table($this->table)
->where('id', $session->id)
->update(['session_id' => $currentSessionId]);
}
return true;
}
- API context'te
session()->getId()bazen boş döner - Cookie decrypt hatası → Session ID alınamaz
- DB update yapılamaz → Eski session ID kalır → Uyumsuzluk
🔁 Aynı Sekmede Çift Login İsteği
Kullanıcı yanlışlıkla login butonuna iki kez tıklarsa veya
browser back/forward ile login sayfasına dönüp tekrar login yaparsa,
iki ayrı registerSession() çağrısı tetiklenir.
token_A (henüz token_B set edilmedi)DELETE WHERE session_id var ama
aynı session_id ile iki login olursa? İlk login'in token'ı silinir!
💾 Browser Storage/Cache Senkronizasyon
Kullanıcı iki sekme açtıysa (Tab A, Tab B), her ikisi de aynı login_token cookie'sini paylaşır. Ancak localStorage/sessionStorage senkronize olmaz.
token_Atoken_A (aynı)clearAllBrowserStorage()
çağrılıyor ama diğer sekme etkilenmiyor → Sonsuz döngü riski.
💥 Database Hata/Rollback Durumu
registerSession()
sırasında DB hatası (connection timeout, deadlock, constraint error) olursa,
INSERT başarısız olur ama cookie set edilir.
DB::table($this->table)->insert([
'user_id' => $user->id,
'session_id' => $sessionId,
'login_token' => $loginToken,
// ... diğer alanlar
]);
// Cookie SET (INSERT'ten sonra, başarı kontrolü YOK!)
cookie()->queue(cookie($cookieName, $loginToken, ...));
- DB INSERT başarısız → Session DB'de yok
- Cookie set edilir → Browser'da
mzb_login_tokenvar - Polling → sessionExists() → DB'de token yok → FALSE
- Sonuç: Anında logout
🧪 Test Adımları (Problemi Yakalamak İçin)
Browser DevTools: Cookie Kontrolü
-
1.
Login yap, F12 aç → Application sekmesi → Cookies
-
2.
mzb_login_tokencookie'sinin değerini kopyala (64 char hex) -
3.
Müzik dinlerken 5 saniye aralıklarla cookie'yi kontrol et → Silinirse Neden 1 ONAYLANDI
-
4.
Console'da
document.cookieçalıştır →mzb_login_tokengörünüyor mu?
Database: user_active_sessions Tablosu
mysql -u tuufi.com_ -p tenant_muzibu_1528d0 -e "
SELECT
id, user_id,
SUBSTRING(login_token, 1, 16) as token,
ip_address, device, browser,
created_at, last_activity
FROM user_active_sessions
WHERE user_id = [KULLANICI_ID]
ORDER BY last_activity DESC;"
- • Kaç session var? 1'den fazlaysa LIFO tetiklenmiş olabilir
- • login_token eşleşiyor mu? Cookie ile DB tokenini karşılaştır
- • last_activity güncel mi? 5 saniyede bir güncellenmeli
Laravel Log: Session Termination Kayıtları
tail -f storage/logs/session-terminations-$(date +%Y-%m-%d).log
Oturum kapanma anında bu log dosyasına session termination kaydı yazılacak. İçinde:
- • reason: lifo, manual, timeout, logout, admin
- • user_email: Kullanıcı maili
- • kicked_session: Kapatılan oturumun IP, cihaz, browser bilgisi
- • new_session: Yeni oturumun (kick eden) bilgisi
reason: 'lifo'
görüyorsan ama gerçekte tek cihazdan girişyaptıysan → Neden 4 ONAYLANDI
(çift login isteği)
Network Tab: API Requests
-
1.
F12 → Network sekmesi → Preserve log aktif et
-
2.
/api/auth/check-sessionisteklerini filtrele -
3.
Her 5 saniyede bir istek görmelisin. Response:
{"valid": true, "user_id": 22}→ OK
{"valid": false, "reason": "session_terminated"}→ PROBLEM -
4.
Headers sekmesi → Request Cookies →
mzb_login_tokengönderiliyor mu?
Timing Analysis: Race Condition Testi
Login butonuna hızlıca iki kez tıkla (double click) →
İki registerSession() çağrısı tetiklenir.
token_A (ilk set edilen)registerSession()
içinde duplicate check yap.
✅ Önerilen Çözümler
📊 Enhanced Logging (Acil - Hemen Uygulanmalı)
DeviceService.php içindeki kritik noktalara detaylı log ekle:
\Log::info('🔐 registerSession: START', [
'user_id' => $user->id,
'session_id' => substr($sessionId, 0, 16) . '...',
'login_token' => substr($loginToken, 0, 16) . '...',
]);
// DB INSERT
$inserted = DB::table($this->table)->insert([...]);
if (!$inserted) {
\Log::error('🔐 registerSession: DB INSERT FAILED', [
'user_id' => $user->id,
]);
return; // Cookie SET ETME!
}
\Log::info('🔐 registerSession: SUCCESS');
🍪 Cookie Validation & Retry Logic
sessionExists() içinde cookie yoksa HEMEN logout etme, önce retry dene.
$cookieToken = request()->cookie('mzb_login_token');
if (!$cookieToken) {
// RETRY: 2 saniye sonra tekrar kontrol et
static $retryCount = 0;
if ($retryCount < 3) {
$retryCount++;
\Log::warning('🔐 sessionExists: Cookie missing, retry #' . $retryCount);
sleep(2);
return $this->sessionExists($user); // Recursive retry
}
\Log::error('🔐 sessionExists: Cookie missing after 3 retries');
return false; // Gerçekten yok
}
🚫 Duplicate Login Request Prevention
Aynı kullanıcının 5 saniye içinde iki kez login yapmasını engelle.
// Aynı kullanıcı son 5 saniyede login yaptı mı?
$recentLogin = DB::table($this->table)
->where('user_id', $user->id)
->where('created_at', '>=', now()->subSeconds(5))
->exists();
if ($recentLogin) {
\Log::warning('🔐 registerSession: Duplicate login attempt (throttled)', [
'user_id' => $user->id,
]);
return; // Yeni session AÇMA, mevcut geçerli
}
⏱️ Login Sonrası İlk Polling Delay Artırma
Login sonrası ilk polling 2 saniye → 5 saniye arttırılmalı.
// ❌ ESKİ: 2 saniye (yavaş network'te yetersiz)
setTimeout(() => {
this.checkSessionValidity();
}, 2000);
// ✅ YENİ: 5 saniye (DB INSERT kesin tamamlanır)
setTimeout(() => {
this.checkSessionValidity();
}, 5000);
🔐 Cookie SameSite Policy Ayarı
Eğer site iframe içinde veya subdomain'lerden erişiliyorsa,
SameSite=None; Secure kullan.
// ❌ ESKİ: SameSite=Lax (iframe'de engellenir)
cookie($cookieName, $loginToken, $cookieMinutes, '/', null, true, true, false, 'Lax');
// ✅ YENİ: SameSite=None (tüm context'lerde çalışır, Secure=true zorunlu)
cookie($cookieName, $loginToken, $cookieMinutes, '/', null, true, true, false, 'None');
💾 DB Transaction & Error Handling
registerSession() içinde DB transaction kullan, hata varsa cookie SET ETME.
try {
DB::beginTransaction();
// Önce bu session'ı sil (varsa)
DB::table($this->table)->where('session_id', $sessionId)->delete();
// Yeni session kaydet
DB::table($this->table)->insert([...]);
// LIFO enforcement
$this->enforceDeviceLimit($user, $sessionId);
DB::commit();
// ✅ BAŞARILI - Cookie set et
cookie()->queue(cookie($cookieName, $loginToken, ...));
} catch (\Exception $e) {
DB::rollBack();
\Log::error('🔐 registerSession: TRANSACTION FAILED', [
'user_id' => $user->id,
'error' => $e->getMessage(),
]);
// ❌ Cookie SET ETME!
return;
}