Session Management

Master session management, token handling, and authentication persistence in your Qlik applications.

Session Management Overview

Effective session management ensures your users have a smooth, secure experience while maintaining proper authentication state across their interactions with Qlik applications.

Token Lifecycle
Automatic token refresh and expiration handling
Security
Secure storage and transmission of authentication data
Auto-Recovery
Automatic session restoration and error recovery

Session Lifecycle Management

Basic Session Operations

Fundamental session management operations

typescript
import Qlik from 'qlik';

class BasicSessionManager {
  private qlik: Qlik;

  constructor(config: QlikConfig) {
    this.qlik = new Qlik(config);
  }

  // Check current authentication status
  async checkSession(): Promise<boolean> {
    try {
      const isAuthenticated = await this.qlik.isAuthenticated();
      console.log('Session status:', isAuthenticated ? 'Active' : 'Inactive');
      return isAuthenticated;
    } catch (error) {
      console.error('Session check failed:', error);
      return false;
    }
  }

  // Initialize new session
  async createSession(): Promise<void> {
    try {
      await this.qlik.authenticateToQlik();
      console.log('✅ New session created');
      
      // Optional: Set up session monitoring
      this.startSessionMonitoring();
    } catch (error) {
      console.error('❌ Session creation failed:', error);
      throw error;
    }
  }

  // End current session
  async endSession(): Promise<void> {
    try {
      await this.qlik.logout();
      console.log('✅ Session ended');
    } catch (error) {
      console.error('Session logout failed:', error);
    }
  }

  // Get session information
  async getSessionInfo(): Promise<any> {
    try {
      const sessionInfo = await this.qlik.getSessionInfo();
      return {
        userId: sessionInfo.userId,
        tenantId: sessionInfo.tenantId,
        expiry: sessionInfo.expiry,
        lastActivity: new Date().toISOString()
      };
    } catch (error) {
      console.error('Failed to get session info:', error);
      return null;
    }
  }

  private startSessionMonitoring(): void {
    // Monitor session every 5 minutes
    setInterval(async () => {
      const isActive = await this.checkSession();
      if (!isActive) {
        console.warn('Session expired, attempting refresh...');
        await this.handleSessionExpired();
      }
    }, 5 * 60 * 1000);
  }

  private async handleSessionExpired(): Promise<void> {
    try {
      await this.createSession();
    } catch (error) {
      console.error('Session refresh failed:', error);
      // Emit session expired event for UI handling
      this.emitSessionExpired();
    }
  }

  private emitSessionExpired(): void {
    window.dispatchEvent(new CustomEvent('qlik:session-expired'));
  }
}

// Usage
const sessionManager = new BasicSessionManager({
  host: 'your-tenant.us.qlikcloud.com',
  webIntegrationId: 'your-web-integration-id'
});

// Initialize session
await sessionManager.createSession();

// Check session status
const isActive = await sessionManager.checkSession();

// Get session details
const sessionInfo = await sessionManager.getSessionInfo();

React Session Management

useSession Hook

React hook for session management with state tracking

typescript
// hooks/useSession.ts
import { useState, useEffect, useCallback, useRef } from 'react';
import Qlik from 'qlik';

interface SessionState {
  isAuthenticated: boolean;
  isLoading: boolean;
  user: any | null;
  error: string | null;
  tokenExpiry: Date | null;
}

interface UseSessionReturn extends SessionState {
  login: () => Promise<void>;
  logout: () => Promise<void>;
  refresh: () => Promise<void>;
  clearError: () => void;
}

export function useSession(qlik: Qlik): UseSessionReturn {
  const [state, setState] = useState<SessionState>({
    isAuthenticated: false,
    isLoading: true,
    user: null,
    error: null,
    tokenExpiry: null
  });

  const refreshTimerRef = useRef<NodeJS.Timeout | null>(null);

  // Initialize session on mount
  useEffect(() => {
    initializeSession();
    
    // Cleanup on unmount
    return () => {
      if (refreshTimerRef.current) {
        clearTimeout(refreshTimerRef.current);
      }
    };
  }, []);

  const initializeSession = async () => {
    try {
      setState(prev => ({ ...prev, isLoading: true, error: null }));
      
      const isAuthenticated = await qlik.isAuthenticated();
      
      if (isAuthenticated) {
        const userInfo = await qlik.getUserInfo();
        const tokenInfo = await qlik.getTokenInfo();
        
        setState(prev => ({
          ...prev,
          isAuthenticated: true,
          user: userInfo,
          tokenExpiry: new Date(tokenInfo.expiry),
          isLoading: false
        }));
        
        scheduleTokenRefresh(new Date(tokenInfo.expiry));
      } else {
        setState(prev => ({
          ...prev,
          isAuthenticated: false,
          user: null,
          tokenExpiry: null,
          isLoading: false
        }));
      }
    } catch (error) {
      setState(prev => ({
        ...prev,
        error: error instanceof Error ? error.message : 'Session initialization failed',
        isLoading: false
      }));
    }
  };

  const login = useCallback(async () => {
    try {
      setState(prev => ({ ...prev, isLoading: true, error: null }));
      
      await qlik.authenticateToQlik();
      
      const userInfo = await qlik.getUserInfo();
      const tokenInfo = await qlik.getTokenInfo();
      
      setState(prev => ({
        ...prev,
        isAuthenticated: true,
        user: userInfo,
        tokenExpiry: new Date(tokenInfo.expiry),
        isLoading: false
      }));
      
      scheduleTokenRefresh(new Date(tokenInfo.expiry));
      
    } catch (error) {
      setState(prev => ({
        ...prev,
        error: error instanceof Error ? error.message : 'Login failed',
        isAuthenticated: false,
        isLoading: false
      }));
    }
  }, [qlik]);

  const logout = useCallback(async () => {
    try {
      await qlik.logout();
      
      if (refreshTimerRef.current) {
        clearTimeout(refreshTimerRef.current);
        refreshTimerRef.current = null;
      }
      
      setState({
        isAuthenticated: false,
        isLoading: false,
        user: null,
        error: null,
        tokenExpiry: null
      });
    } catch (error) {
      console.error('Logout failed:', error);
    }
  }, [qlik]);

  const refresh = useCallback(async () => {
    await initializeSession();
  }, []);

  const clearError = useCallback(() => {
    setState(prev => ({ ...prev, error: null }));
  }, []);

  const scheduleTokenRefresh = (expiry: Date) => {
    const refreshTime = expiry.getTime() - Date.now() - (5 * 60 * 1000); // 5 min buffer
    
    if (refreshTime > 0) {
      refreshTimerRef.current = setTimeout(async () => {
        try {
          await qlik.refreshToken();
          await initializeSession();
        } catch (error) {
          console.error('Token refresh failed:', error);
          await logout();
        }
      }, refreshTime);
    }
  };

  return {
    ...state,
    login,
    logout,
    refresh,
    clearError
  };
}

// Component usage
function SessionProvider({ children }: { children: React.ReactNode }) {
  const qlik = new Qlik({
    host: 'your-tenant.us.qlikcloud.com',
    webIntegrationId: 'your-web-integration-id'
  });
  
  const session = useSession(qlik);

  if (session.isLoading) {
    return <div>Loading session...</div>;
  }

  if (session.error) {
    return (
      <div>
        <p>Session error: {session.error}</p>
        <button onClick={session.clearError}>Retry</button>
      </div>
    );
  }

  if (!session.isAuthenticated) {
    return (
      <div>
        <button onClick={session.login}>Sign In</button>
      </div>
    );
  }

  return (
    <div>
      <div className="session-info">
        <span>Welcome, {session.user?.name}</span>
        <span>Token expires: {session.tokenExpiry?.toLocaleTimeString()}</span>
        <button onClick={session.logout}>Sign Out</button>
      </div>
      {children}
    </div>
  );
}

Session Security

Secure Token Storage

Best practices for storing and managing authentication tokens

typescript
class SecureTokenStorage {
  private readonly TOKEN_KEY = 'qlik_auth_token';
  private readonly EXPIRY_KEY = 'qlik_token_expiry';
  private readonly MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours

  // Store token securely
  storeToken(token: string, expiry: string): boolean {
    try {
      // Validate token before storing
      if (!this.validateToken(token)) {
        throw new Error('Invalid token format');
      }

      const expiryTime = new Date(expiry).getTime();
      const maxExpiryTime = Date.now() + this.MAX_AGE;

      // Ensure expiry is not too far in the future
      if (expiryTime > maxExpiryTime) {
        throw new Error('Token expiry exceeds maximum allowed time');
      }

      // Store with additional metadata
      const tokenData = {
        token,
        expiry,
        stored: Date.now(),
        origin: window.location.origin
      };

      localStorage.setItem(this.TOKEN_KEY, JSON.stringify(tokenData));
      return true;

    } catch (error) {
      console.error('Token storage failed:', error);
      return false;
    }
  }

  // Retrieve token with validation
  getToken(): { token: string; expiry: string } | null {
    try {
      const stored = localStorage.getItem(this.TOKEN_KEY);
      
      if (!stored) {
        return null;
      }

      const tokenData = JSON.parse(stored);

      // Validate stored data structure
      if (!tokenData.token || !tokenData.expiry || !tokenData.origin) {
        this.clearToken();
        return null;
      }

      // Verify origin matches (security check)
      if (tokenData.origin !== window.location.origin) {
        this.clearToken();
        return null;
      }

      // Check if token is expired
      const expiryTime = new Date(tokenData.expiry).getTime();
      if (expiryTime <= Date.now()) {
        this.clearToken();
        return null;
      }

      return {
        token: tokenData.token,
        expiry: tokenData.expiry
      };

    } catch (error) {
      console.error('Token retrieval failed:', error);
      this.clearToken();
      return null;
    }
  }

  // Clear stored tokens
  clearToken(): void {
    try {
      localStorage.removeItem(this.TOKEN_KEY);
    } catch (error) {
      console.error('Token clearing failed:', error);
    }
  }

  // Validate token format
  private validateToken(token: string): boolean {
    // Basic JWT validation
    if (!token || typeof token !== 'string') {
      return false;
    }

    const parts = token.split('.');
    if (parts.length !== 3) {
      return false; // Not a valid JWT structure
    }

    try {
      // Try to decode header and payload
      atob(parts[0]);
      atob(parts[1]);
      return true;
    } catch {
      return false;
    }
  }

  // Check if storage is available
  isStorageAvailable(): boolean {
    try {
      const testKey = '__storage_test__';
      localStorage.setItem(testKey, 'test');
      localStorage.removeItem(testKey);
      return true;
    } catch {
      return false;
    }
  }
}

// Usage with session manager
class SecureSessionManager {
  private storage = new SecureTokenStorage();
  private qlik: Qlik;

  constructor(config: QlikConfig) {
    this.qlik = new Qlik(config);
  }

  async initializeSecureSession(): Promise<void> {
    if (!this.storage.isStorageAvailable()) {
      console.warn('Secure storage not available, using memory-only session');
    }

    // Try to restore from secure storage
    const storedTokens = this.storage.getToken();
    
    if (storedTokens) {
      try {
        this.qlik.setAccessToken(storedTokens.token);
        
        // Validate the restored token
        const isValid = await this.qlik.isAuthenticated();
        
        if (isValid) {
          console.log('✅ Secure session restored');
          return;
        }
      } catch (error) {
        console.warn('Stored token validation failed:', error);
      }
    }

    // Clear invalid tokens and start fresh
    this.storage.clearToken();
    await this.qlik.authenticateToQlik();
    
    // Store new tokens securely
    const token = await this.qlik.getAccessToken();
    const tokenInfo = await this.qlik.getTokenInfo();
    
    this.storage.storeToken(token, tokenInfo.expiry);
    console.log('✅ New secure session established');
  }
}

⚡ Session Management Best Practices

Automatic Refresh: Implement proactive token refresh before expiration
Secure Storage: Use secure storage methods and validate stored tokens
Error Recovery: Handle network issues and authentication failures gracefully
User Experience: Provide clear feedback on session status and actions
Memory Management: Clean up timers and event listeners on component unmount
Multi-tab Support: Coordinate sessions across multiple browser tabs
Offline Handling: Gracefully handle offline scenarios and reconnection
Security Validation: Always validate tokens and session data integrity