Session Management
Secure session configuration, monitoring, and lifecycle management for the application.
Session Management
Secure session management is crucial for maintaining user authentication state and preventing session-based attacks. This guide covers session configuration, security monitoring, and best practices.
Session Configuration
Secure Session Settings
// lib/auth/session.ts
export const sessionConfig = {
// Session timeout (8 hours)
maxAge: 8 * 60 * 60,
// Secure cookie settings
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict' as const,
// Session rotation
rotationInterval: 30 * 60, // 30 minutes
// Concurrent session limit
maxConcurrentSessions: 3
}
export async function createSession(userId: string, metadata: any) {
const sessionId = crypto.randomUUID()
const expiresAt = new Date(Date.now() + sessionConfig.maxAge * 1000)
// Store session in database
await supabase
.from('user_sessions')
.insert({
id: sessionId,
user_id: userId,
expires_at: expiresAt.toISOString(),
ip_address: metadata.ip,
user_agent: metadata.userAgent
})
return sessionId
}
export async function validateSession(sessionId: string) {
const { data: session } = await supabase
.from('user_sessions')
.select('*')
.eq('id', sessionId)
.gte('expires_at', new Date().toISOString())
.single()
if (!session) {
throw new Error('Invalid or expired session')
}
// Check for suspicious activity
await checkSessionSecurity(session)
return session
}
Session Rotation
// lib/auth/rotation.ts
export async function rotateSession(sessionId: string) {
const oldSession = await validateSession(sessionId)
// Create new session
const newSessionId = crypto.randomUUID()
const expiresAt = new Date(Date.now() + sessionConfig.maxAge * 1000)
// Update session in database
await supabase
.from('user_sessions')
.update({
id: newSessionId,
expires_at: expiresAt.toISOString(),
rotated_at: new Date().toISOString(),
previous_session_id: sessionId
})
.eq('id', sessionId)
// Log rotation
await createAuditLog({
action: 'session_rotated',
user_id: oldSession.user_id,
details: {
old_session_id: sessionId,
new_session_id: newSessionId
}
})
return newSessionId
}
// Automatic session rotation middleware
export async function sessionRotationMiddleware(request: NextRequest) {
const sessionId = request.cookies.get('session_id')?.value
if (!sessionId) return NextResponse.next()
try {
const session = await validateSession(sessionId)
const sessionAge = Date.now() - new Date(session.created_at).getTime()
// Rotate if session is older than rotation interval
if (sessionAge > sessionConfig.rotationInterval * 1000) {
const newSessionId = await rotateSession(sessionId)
const response = NextResponse.next()
response.cookies.set('session_id', newSessionId, {
...sessionConfig,
maxAge: sessionConfig.maxAge
})
return response
}
return NextResponse.next()
} catch (error) {
// Invalid session, redirect to login
return NextResponse.redirect(new URL('/auth/login', request.url))
}
}
Session Security Monitoring
Security Checks
// lib/auth/security.ts
export async function checkSessionSecurity(session: any) {
const now = new Date()
const sessionAge = now.getTime() - new Date(session.created_at).getTime()
// Force re-authentication for old sessions
if (sessionAge > sessionConfig.rotationInterval * 1000) {
await rotateSession(session.id)
}
// Check for suspicious location changes
const recentSessions = await supabase
.from('user_sessions')
.select('ip_address, created_at')
.eq('user_id', session.user_id)
.gte('created_at', new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString())
.order('created_at', { ascending: false })
if (recentSessions.data) {
const ips = recentSessions.data.map(s => s.ip_address)
const uniqueIPs = new Set(ips)
// Alert on multiple IPs in short time
if (uniqueIPs.size > 3) {
await createSecurityAlert({
user_id: session.user_id,
type: 'suspicious_location_change',
details: { ip_addresses: Array.from(uniqueIPs) }
})
}
}
}
export async function detectConcurrentSessions(userId: string) {
const activeSessions = await supabase
.from('user_sessions')
.select('*')
.eq('user_id', userId)
.gte('expires_at', new Date().toISOString())
if (activeSessions.data && activeSessions.data.length > sessionConfig.maxConcurrentSessions) {
// Keep only the most recent sessions
const sortedSessions = activeSessions.data.sort(
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
)
const sessionsToTerminate = sortedSessions.slice(sessionConfig.maxConcurrentSessions)
for (const session of sessionsToTerminate) {
await terminateSession(session.id, 'concurrent_session_limit')
}
// Alert user about terminated sessions
await sendNotification({
user_id: userId,
type: 'security_alert',
message: `Some of your sessions were terminated due to concurrent login limit (${sessionConfig.maxConcurrentSessions})`
})
}
}
Session Lifecycle Management
// lib/auth/lifecycle.ts
export async function terminateSession(sessionId: string, reason?: string) {
await supabase
.from('user_sessions')
.delete()
.eq('id', sessionId)
// Log session termination
await createAuditLog({
action: 'session_terminated',
details: { session_id: sessionId, reason }
})
}
export async function terminateAllUserSessions(userId: string, excludeSessionId?: string) {
let query = supabase
.from('user_sessions')
.delete()
.eq('user_id', userId)
if (excludeSessionId) {
query = query.neq('id', excludeSessionId)
}
await query
await createAuditLog({
action: 'all_sessions_terminated',
user_id: userId,
details: { excluded_session: excludeSessionId }
})
}
export async function cleanupExpiredSessions() {
const { data: expiredSessions } = await supabase
.from('user_sessions')
.select('id, user_id')
.lt('expires_at', new Date().toISOString())
if (expiredSessions && expiredSessions.length > 0) {
await supabase
.from('user_sessions')
.delete()
.lt('expires_at', new Date().toISOString())
await createAuditLog({
action: 'expired_sessions_cleanup',
details: {
count: expiredSessions.length,
session_ids: expiredSessions.map(s => s.id)
}
})
}
}
// Schedule cleanup job
export function scheduleSessionCleanup() {
setInterval(async () => {
await cleanupExpiredSessions()
}, 60 * 60 * 1000) // Run every hour
}
Session Monitoring
Real-time Session Tracking
// lib/auth/monitoring.ts
export class SessionMonitor {
static async trackSessionActivity(sessionId: string, activity: string) {
await supabase
.from('session_activities')
.insert({
session_id: sessionId,
activity,
timestamp: new Date().toISOString()
})
}
static async getSessionMetrics(userId: string) {
const sessions = await supabase
.from('user_sessions')
.select(`
*,
session_activities(count)
`)
.eq('user_id', userId)
.gte('expires_at', new Date().toISOString())
return {
active_sessions: sessions.data?.length || 0,
total_activity: sessions.data?.reduce((sum, session) =>
sum + (session.session_activities?.length || 0), 0
) || 0,
locations: [...new Set(sessions.data?.map(s => s.ip_address) || [])],
devices: [...new Set(sessions.data?.map(s => s.user_agent) || [])]
}
}
static async detectAnomalousActivity(sessionId: string) {
const activities = await supabase
.from('session_activities')
.select('*')
.eq('session_id', sessionId)
.gte('timestamp', new Date(Date.now() - 60 * 60 * 1000).toISOString())
.order('timestamp', { ascending: false })
if (activities.data) {
const activityCount = activities.data.length
const uniqueActivities = new Set(activities.data.map(a => a.activity))
// Alert on excessive activity
if (activityCount > 1000) {
await this.createSecurityAlert('excessive_session_activity', {
session_id: sessionId,
activity_count: activityCount,
unique_activities: Array.from(uniqueActivities)
})
}
}
}
private static async createSecurityAlert(type: string, details: any) {
await supabase
.from('security_alerts')
.insert({
type,
details,
severity: 'medium',
created_at: new Date().toISOString()
})
}
}
Best Practices
Session Security Guidelines
-
Always use HTTPS in production
- Encrypt session tokens in transit
- Set secure cookie flags
-
Implement proper session timeout
- Idle timeout for inactive sessions
- Absolute timeout for maximum session duration
-
Use secure session storage
- HTTP-only cookies to prevent XSS
- SameSite attribute to prevent CSRF
-
Monitor session activities
- Track login locations and devices
- Alert on suspicious activities
-
Implement session rotation
- Regularly rotate session IDs
- Invalidate old sessions
Session Management Checklist
- Secure session configuration
- Session timeout implementation
- Session rotation mechanism
- Concurrent session limits
- Session activity monitoring
- Automatic cleanup of expired sessions
- Security alerts for suspicious activities
- Proper session termination on logout
- Session invalidation on security events
- Regular security audits of session handling
Integration with Authentication
// lib/auth/integration.ts
export async function handleUserLogin(credentials: LoginCredentials) {
// Authenticate user
const { data: { user }, error } = await supabase.auth.signInWithPassword(credentials)
if (error || !user) {
throw new Error('Authentication failed')
}
// Create session
const sessionId = await createSession(user.id, {
ip: credentials.ip,
userAgent: credentials.userAgent
})
// Check concurrent sessions
await detectConcurrentSessions(user.id)
// Track login activity
await SessionMonitor.trackSessionActivity(sessionId, 'login')
return { user, sessionId }
}
export async function handleUserLogout(sessionId: string) {
// Track logout activity
await SessionMonitor.trackSessionActivity(sessionId, 'logout')
// Terminate session
await terminateSession(sessionId, 'user_logout')
// Clear session cookie
return { success: true }
}
This comprehensive session management system ensures secure authentication state management while providing monitoring and security features to protect against session-based attacks.