Starship Rewards API

Session Management

Handle user sessions, timeouts, and multi-tab synchronization

Session Management

Advanced session management techniques for handling user sessions, automatic timeouts, and synchronizing authentication state across multiple browser tabs or application instances.

Session Timeout Handling

Implement automatic session timeout with user activity tracking:

# Check session status
curl -X GET {{host}}/auth/session/status \
  -H "Authorization: Bearer your_access_token" \
  -H "Cookie: session_id=your_session_id"

# Response:
# {
#   "valid": true,
#   "remaining_time": 1800,
#   "warning_threshold": 300,
#   "last_activity": "2024-01-15T10:30:00Z"
# }

# Extend session
curl -X POST {{host}}/auth/session/extend \
  -H "Authorization: Bearer your_access_token" \
  -H "Cookie: session_id=your_session_id"

# Activity tracking (automatic on API calls)
curl -X POST {{host}}/api/any-endpoint \
  -H "Authorization: Bearer your_access_token" \
  -H "Cookie: session_id=your_session_id"
# This call automatically updates last activity timestamp
<?php
class SessionManager {
    private $sessionTimeout;
    private $warningTime;
    private $sessionKey;

    public function __construct($timeoutMinutes = 30, $warningMinutes = 5) {
        $this->sessionTimeout = $timeoutMinutes * 60; // Convert to seconds
        $this->warningTime = $warningMinutes * 60;
        $this->sessionKey = 'last_activity';

        // Start session if not already started
        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }

        $this->updateActivity();
    }

    public function updateActivity() {
        $_SESSION[$this->sessionKey] = time();
    }

    public function isSessionValid() {
        if (!isset($_SESSION[$this->sessionKey])) {
            return false;
        }

        $lastActivity = $_SESSION[$this->sessionKey];
        $currentTime = time();

        return ($currentTime - $lastActivity) < $this->sessionTimeout;
    }

    public function shouldShowWarning() {
        if (!isset($_SESSION[$this->sessionKey])) {
            return false;
        }

        $lastActivity = $_SESSION[$this->sessionKey];
        $currentTime = time();
        $timeSinceActivity = $currentTime - $lastActivity;

        return $timeSinceActivity >= ($this->sessionTimeout - $this->warningTime) &&
               $timeSinceActivity < $this->sessionTimeout;
    }

    public function getRemainingTime() {
        if (!isset($_SESSION[$this->sessionKey])) {
            return 0;
        }

        $lastActivity = $_SESSION[$this->sessionKey];
        $currentTime = time();
        $remaining = $this->sessionTimeout - ($currentTime - $lastActivity);

        return max(0, $remaining);
    }

    public function checkSessionAndRedirect($loginUrl = '/login') {
        if (!$this->isSessionValid()) {
            session_destroy();
            header('Location: ' . $loginUrl);
            exit;
        }

        // Update activity on each check
        $this->updateActivity();
    }

    // AJAX endpoint for session status
    public function getSessionStatus() {
        return [
            'valid' => $this->isSessionValid(),
            'showWarning' => $this->shouldShowWarning(),
            'remainingTime' => $this->getRemainingTime(),
            'warningThreshold' => $this->warningTime
        ];
    }
}
?>

Multi-Tab Synchronization

Synchronize authentication state across multiple browser tabs:

# Multi-tab synchronization is typically handled client-side
# Server provides events endpoint for polling

# Poll for authentication events
curl -X GET "{{host}}/auth/events?since=1642248000" \
  -H "Authorization: Bearer your_access_token" \
  -H "Cookie: session_id=your_session_id"

# Response:
# {
#   "events": [
#     {
#       "type": "LOGIN",
#       "timestamp": 1642248123,
#       "data": {
#         "session_id": "new_session_123"
#       }
#     },
#     {
#       "type": "LOGOUT",
#       "timestamp": 1642248456,
#       "data": null
#     }
#   ]
# }

# Broadcast logout to all sessions
curl -X POST {{host}}/auth/logout-all \
  -H "Authorization: Bearer your_access_token"

# WebSocket connection for real-time events
curl -i -N -H "Connection: Upgrade" \
     -H "Upgrade: websocket" \
     -H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
     -H "Sec-WebSocket-Version: 13" \
     "{{host}}/auth/events/ws"
<?php
// Note: PHP doesn't have built-in multi-tab synchronization
// This implementation uses server-side session management and AJAX polling

class MultiTabSessionManager {
    private $redis; // Redis for cross-tab communication
    private $eventChannel;

    public function __construct($redis = null) {
        $this->redis = $redis;
        $this->eventChannel = "starship_auth_events_" . session_id();

        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }
    }

    public function broadcastAuthEvent($type, $data = []) {
        $event = [
            'type' => $type,
            'data' => $data,
            'timestamp' => time(),
            'session_id' => session_id()
        ];

        if ($this->redis) {
            // Publish to Redis channel
            $this->redis->publish($this->eventChannel, json_encode($event));
        }

        // Also store in session for HTTP polling fallback
        $this->storeEventInSession($type, $data);
    }

    private function storeEventInSession($type, $data) {
        if (!isset($_SESSION['auth_events'])) {
            $_SESSION['auth_events'] = [];
        }

        $_SESSION['auth_events'][] = [
            'type' => $type,
            'data' => $data,
            'timestamp' => time()
        ];

        // Keep only recent events (last 10)
        $_SESSION['auth_events'] = array_slice($_SESSION['auth_events'], -10);
    }

    public function getRecentEvents($since = null) {
        if (!isset($_SESSION['auth_events'])) {
            return [];
        }

        if ($since === null) {
            return $_SESSION['auth_events'];
        }

        return array_filter($_SESSION['auth_events'], function($event) use ($since) {
            return $event['timestamp'] > $since;
        });
    }

    public function login($tokens) {
        $_SESSION['access_token'] = $tokens['access_token'];
        $_SESSION['refresh_token'] = $tokens['refresh_token'] ?? null;

        $this->broadcastAuthEvent('LOGIN', ['tokens' => $tokens]);
    }

    public function logout() {
        unset($_SESSION['access_token']);
        unset($_SESSION['refresh_token']);

        $this->broadcastAuthEvent('LOGOUT');
    }

    // AJAX endpoint for polling events
    public static function handleEventPolling() {
        $sessionManager = new self();
        $since = $_GET['since'] ?? null;

        header('Content-Type: application/json');

        if ($since) {
            $since = (int) $since;
        }

        $events = $sessionManager->getRecentEvents($since);
        echo json_encode(['events' => $events]);
    }
}
?>

<script>
// Client-side JavaScript for PHP backend
class PHPMultiTabAuth {
    constructor(pollingInterval = 2000) {
        this.pollingInterval = pollingInterval;
        this.lastEventTime = Math.floor(Date.now() / 1000);
        this.eventHandlers = new Map();
        this.isPolling = false;

        this.startPolling();
    }

    startPolling() {
        if (this.isPolling) return;
        this.isPolling = true;

        const poll = async () => {
            try {
                const response = await fetch(`/api/auth/events?since=${this.lastEventTime}`);
                const data = await response.json();

                data.events.forEach(event => {
                    this.handleAuthEvent(event);
                    this.lastEventTime = Math.max(this.lastEventTime, event.timestamp);
                });
            } catch (error) {
                console.error('Error polling auth events:', error);
            }

            if (this.isPolling) {
                setTimeout(poll, this.pollingInterval);
            }
        };

        poll();
    }

    handleAuthEvent(event) {
        const handlers = this.eventHandlers.get(event.type) || [];
        handlers.forEach(handler => {
            try {
                handler(event.data);
            } catch (error) {
                console.error('Error handling auth event:', error);
            }
        });
    }

    on(eventType, handler) {
        if (!this.eventHandlers.has(eventType)) {
            this.eventHandlers.set(eventType, []);
        }
        this.eventHandlers.get(eventType).push(handler);
    }
}
</script>

Implementation Patterns

Combined Session and Multi-Tab Management

class ComprehensiveAuthManager {
  private sessionManager: SessionManager;
  private multiTabManager: MultiTabAuthManager;
  private tokenManager: TokenManager;

  constructor() {
    this.tokenManager = new TokenManager();
    this.multiTabManager = new MultiTabAuthManager();
    this.sessionManager = new SessionManager(
      {
        timeoutMinutes: 30,
        warningMinutes: 5,
        activityEvents: ['mousedown', 'keydown', 'scroll']
      },
      () => this.handleSessionExpired()
    );

    this.setupEventHandlers();
  }

  private setupEventHandlers(): void {
    // Handle multi-tab events
    this.multiTabManager.on('LOGIN', (data) => {
      this.tokenManager.setTokens(data.tokens.access_token, data.tokens.refresh_token, data.tokens.expires_in);
    });

    this.multiTabManager.on('LOGOUT', () => {
      this.handleLogout();
    });

    this.multiTabManager.on('SESSION_EXPIRED', () => {
      this.handleSessionExpired();
    });
  }

  async login(credentials: any): Promise<void> {
    const tokens = await this.authenticate(credentials);
    this.tokenManager.setTokens(tokens.access_token, tokens.refresh_token, tokens.expires_in);
    this.sessionManager.updateActivity();
    this.multiTabManager.login(tokens);
  }

  logout(): void {
    this.tokenManager.clearTokens();
    this.sessionManager.destroy();
    this.multiTabManager.logout();
    this.handleLogout();
  }

  private handleSessionExpired(): void {
    this.tokenManager.clearTokens();
    this.multiTabManager.onSessionExpired();
    window.location.href = '/login';
  }

  private handleLogout(): void {
    window.location.href = '/login';
  }
}

Summary

  • Session timeouts: Implement automatic session expiration with user activity tracking
  • Multi-tab sync: Keep authentication state synchronized across browser tabs
  • User warnings: Give users advance notice before session expiration
  • Graceful handling: Provide smooth user experience during session transitions
  • Background monitoring: Use timers and event listeners to track session state

This comprehensive session management ensures users have a seamless experience while maintaining security through proper session lifecycle management.

For additional security patterns, see Security Considerations.