Session Management

Atlantic ID entegrasyonunda session yönetimi best practices.


Session Storage

Backend Session

Önerilen yaklaşım: Token'ları backend session'da saklayın.

// Express.js
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,      // HTTPS only
    httpOnly: true,    // XSS koruması
    sameSite: 'lax',   // CSRF koruması
    maxAge: 86400000   // 24 saat
  },
  store: new RedisStore({
    client: redisClient,
    prefix: 'sess:'
  })
}));

// Token'ları session'a kaydet
req.session.accessToken = tokens.access_token;
req.session.refreshToken = tokens.refresh_token;
req.session.user = userInfo;

Cookie-Based

// httpOnly cookie ile token gönder
res.cookie('access_token', tokens.access_token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 900000 // 15 dakika
});

Session Lifecycle

1. Session Creation

app.get('/auth/callback', async (req, res) => {
  // Token exchange
  const tokens = await exchangeCode(req.query.code);
  const userInfo = await getUserInfo(tokens.access_token);

  // Session oluştur
  req.session.regenerate((err) => {
    if (err) return res.status(500).send('Session error');

    req.session.userId = userInfo.sub;
    req.session.accessToken = tokens.access_token;
    req.session.refreshToken = tokens.refresh_token;
    req.session.expiresAt = Date.now() + (tokens.expires_in * 1000);

    req.session.save((err) => {
      if (err) return res.status(500).send('Session save error');
      res.redirect('/dashboard');
    });
  });
});

2. Session Validation

function requireAuth(req, res, next) {
  if (!req.session.userId) {
    return res.redirect('/login');
  }

  // Token expired kontrolü
  if (Date.now() >= req.session.expiresAt) {
    return refreshSession(req, res, next);
  }

  next();
}

async function refreshSession(req, res, next) {
  try {
    const tokens = await refreshAccessToken(req.session.refreshToken);

    req.session.accessToken = tokens.access_token;
    req.session.refreshToken = tokens.refresh_token;
    req.session.expiresAt = Date.now() + (tokens.expires_in * 1000);

    await req.session.save();
    next();
  } catch (error) {
    req.session.destroy();
    res.redirect('/login');
  }
}

3. Session Refresh

async function refreshAccessToken(refreshToken) {
  const response = await fetch('https://id.codeatlantis.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET
    })
  });

  if (!response.ok) {
    throw new Error('Refresh failed');
  }

  return await response.json();
}

4. Session Destruction

app.post('/logout', (req, res) => {
  const refreshToken = req.session.refreshToken;

  // Token'ı revoke et
  revokeToken(refreshToken).catch(console.error);

  // Session'ı yok et
  req.session.destroy((err) => {
    if (err) console.error('Session destroy error:', err);
    res.clearCookie('connect.sid');
    res.redirect('/');
  });
});

Session Security

Session Fixation Prevention

// Login sonrası session ID değiştir
req.session.regenerate((err) => {
  req.session.userId = user.id;
});

Session Hijacking Prevention

// IP ve User-Agent kontrolü
req.session.ip = req.ip;
req.session.userAgent = req.headers['user-agent'];

function validateSession(req) {
  if (req.session.ip !== req.ip) {
    throw new Error('IP mismatch');
  }
  if (req.session.userAgent !== req.headers['user-agent']) {
    throw new Error('User-Agent mismatch');
  }
}

Inactivity Timeout

const INACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 dakika

function checkInactivity(req, res, next) {
  const lastActivity = req.session.lastActivity;

  if (lastActivity && Date.now() - lastActivity > INACTIVITY_TIMEOUT) {
    req.session.destroy();
    return res.redirect('/login?reason=timeout');
  }

  req.session.lastActivity = Date.now();
  next();
}

Multi-Tab Support

Aynı kullanıcının birden fazla tab'de oturum açması:

// Frontend: BroadcastChannel ile tab'ler arası iletişim
const authChannel = new BroadcastChannel('auth_channel');

authChannel.onmessage = (event) => {
  if (event.data === 'logout') {
    window.location.href = '/login';
  }
};

// Logout olunca diğer tab'leri bilgilendir
function logout() {
  authChannel.postMessage('logout');
  // ... logout logic
}

Session Storage Options

1. In-Memory (Development Only)

app.use(session({
  secret: 'dev-secret',
  resave: false,
  saveUninitialized: false
}));

❌ Production'da kullanmayın - restart'ta kaybolur

2. Redis (Önerilen)

const RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false
}));

✅ Hızlı, scalable, distributed systems için ideal

3. Database

const MySQLStore = require('express-mysql-session')(session);

app.use(session({
  store: new MySQLStore(dbConfig),
  secret: process.env.SESSION_SECRET
}));

✅ Persistent, backup kolay


Best Practices

  1. Regenerate on login - Session fixation koruması
  2. Use secure cookies - httpOnly, secure, sameSite
  3. Implement timeout - Inactivity ve absolute timeout
  4. Store minimal data - Sadece gerekli bilgiler
  5. Use Redis/DB - In-memory production'da yok
  6. Monitor sessions - Aktif session sayısı, ortalama süre

İlgili: Security, Token Refresh, Logout