JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q52533F
  • License MIT

A flexible token session manager for handling access/refresh token pairs with automatic refresh and cross-domain support

Package Exports

  • manage-token-sessions

Readme

Token Session Manager

A flexible token session manager for handling access/refresh token pairs with automatic refresh and cross-domain support. Inspired by Auth0's battle-tested approach to token management.

Features

  • ๐Ÿ”„ Automatic Token Refresh: Proactively refreshes tokens before expiration
  • ๐Ÿช Flexible Storage: Support for localStorage, sessionStorage, cookies, and custom storage
  • ๐ŸŒ Cross-Domain Support: Cookie storage with subdomain support for multi-app authentication
  • ๐Ÿ”’ JWT Decoding: Extracts expiration times from JWT tokens without verification
  • ๐Ÿ”— Cross-Tab Synchronization: Prevents concurrent refresh attempts across browser tabs
  • ๐Ÿ‘๏ธ Tab Focus Detection: Automatically checks auth state when tab gains focus
  • ๐ŸŽฃ Lifecycle Hooks: Callbacks for session events (started, refreshed, expired, errors)
  • ๐Ÿงช Well Tested: Comprehensive test suite with 100% coverage
  • ๐Ÿ“ฆ TypeScript: Full TypeScript support with detailed type definitions
  • ๐Ÿชถ Lightweight: Minimal dependencies, tree-shakeable

Installation

npm install manage-token-sessions
# or
yarn add manage-token-sessions
# or
pnpm add manage-token-sessions

Quick Start

import { TokenSessionManager, LocalStorageAdapter } from 'manage-token-sessions';

// Define your refresh function
const refreshTokens = async (refreshToken: string) => {
  const response = await fetch('/api/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken })
  });
  
  const data = await response.json();
  return {
    accessToken: data.accessToken,
    refreshToken: data.refreshToken
  };
};

// Create the session manager
const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new LocalStorageAdapter(),
  onSessionRefreshed: (session) => {
    console.log('Session refreshed!', session);
  },
  onSessionExpired: () => {
    console.log('Session expired, redirecting to login...');
    window.location.href = '/login';
  }
});

// Start a session after login
await sessionManager.startSession({
  accessToken: 'your-jwt-access-token',
  refreshToken: 'your-refresh-token'
});

// Get current access token (automatically refreshes if needed)
const accessToken = await sessionManager.getCurrentAccessToken();

// Use the token in API calls
fetch('/api/protected', {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
});

Storage Options

LocalStorage (Default)

import { LocalStorageAdapter } from 'manage-token-sessions';

const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new LocalStorageAdapter()
});

SessionStorage

import { SessionStorageAdapter } from 'manage-token-sessions';

const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new SessionStorageAdapter()
});

Cookies (Cross-Domain Support)

import { CookieStorageAdapter } from 'manage-token-sessions';

// Basic cookie storage
const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new CookieStorageAdapter()
});

// Cross-subdomain cookie storage
const crossDomainSessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new CookieStorageAdapter({
    domain: '.example.com', // Works across app1.example.com, app2.example.com, etc.
    secure: true,
    sameSite: 'lax'
  })
});

Memory Storage (Testing)

import { MemoryStorageAdapter } from 'manage-token-sessions';

const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new MemoryStorageAdapter()
});

Custom Storage

import { TokenStorage } from 'manage-token-sessions';

class CustomStorageAdapter implements TokenStorage {
  async get(key: string): Promise<string | null> {
    // Your custom get implementation
    return null;
  }
  
  async set(key: string, value: string): Promise<void> {
    // Your custom set implementation
  }
  
  async remove(key: string): Promise<void> {
    // Your custom remove implementation
  }
}

const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new CustomStorageAdapter()
});

Configuration Options

const sessionManager = new TokenSessionManager({
  // Required: Function to refresh tokens
  refreshTokenFn: async (refreshToken) => ({ accessToken, refreshToken }),

  // Optional: Storage adapter (default: LocalStorageAdapter)
  storage: new LocalStorageAdapter(),

  // Optional: Storage key (default: '@token-sessions@')
  storageKey: 'my-app-session',

  // Optional: Refresh buffer in seconds (default: 60)
  // Tokens are refreshed this many seconds before expiry
  expiryBufferSeconds: 120,

  // Optional: Refresh check interval in milliseconds (default: 30000)
  refreshIntervalMs: 15000,

  // Optional: Cross-tab lock configuration
  lockOptions: {
    timeout: 5000,    // Lock timeout in milliseconds
    retries: 10,      // Number of retry attempts
    retryDelay: 100   // Delay between retries
  },

  // Optional: Tab focus authentication check (default: true)
  checkOnFocus: true,           // Check auth state when tab gains focus
  focusDebounce: 100,           // Debounce focus events in milliseconds

  // Optional: Lifecycle hooks
  onSessionStarted: (session) => console.log('Session started', session),
  onSessionRefreshed: (session) => console.log('Session refreshed', session),
  onSessionExpired: () => console.log('Session expired'),
  onSessionError: (error) => console.error('Session error', error),
  onRefreshError: (error) => console.error('Refresh error', error),
  onFocusCheck: (session) => console.log('Focus check completed', session)
});

API Reference

TokenSessionManager

Methods

  • startSession(tokens, metadata?): Start a new session
  • getCurrentAccessToken(): Get current access token (auto-refreshes if needed)
  • getCurrentSession(): Get current session data
  • refreshSession(): Manually refresh the session
  • checkAuthState(): Manually trigger a focus check and return current session
  • endSession(): End the current session
  • hasActiveSession(): Check if there's an active session
  • destroy(): Clean up resources

Events

  • onSessionStarted(session): Called when a session is started
  • onSessionRefreshed(session): Called when tokens are refreshed
  • onSessionExpired(): Called when session expires
  • onSessionError(error): Called on session errors
  • onRefreshError(error): Called on refresh errors

Cross-Tab Synchronization

The package automatically prevents concurrent token refresh attempts across multiple browser tabs using a lock mechanism (similar to Auth0's approach). This ensures that:

  • Only one tab refreshes tokens at a time
  • Other tabs wait for the refresh to complete
  • No duplicate refresh requests are made
  • Token consistency is maintained across all tabs
// Multiple tabs with the same session manager configuration
// will automatically coordinate refresh attempts

const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new LocalStorageAdapter(),
  // Lock configuration (optional)
  lockOptions: {
    timeout: 5000,  // Wait up to 5 seconds for lock
    retries: 10,    // Retry 10 times if lock fails
    retryDelay: 100 // Wait 100ms between retries
  }
});

// Tab 1: Triggers refresh
await sessionManager.refreshSession();

// Tab 2: Waits for Tab 1 to complete, then uses the new token
const token = await sessionManager.getCurrentAccessToken();

Tab Focus Authentication Check

The package automatically checks authentication state when a browser tab gains focus. This provides instant feedback when users switch between tabs and ensures the UI reflects the current session state immediately.

How it works:

  • Automatic Detection: When you switch to a tab, it immediately validates the current session
  • Instant Sync: No waiting for the periodic refresh interval (default 30 seconds)
  • Cross-Tab Login: If logged in from another tab, switching back shows the authenticated state instantly
  • Cross-Tab Logout: If logged out in another tab, switching back shows the logged-out state instantly
  • Session Validation: Checks if stored tokens are still valid and not expired
  • Clock Synchronization: Aligns refresh timers with tokens refreshed by other tabs

Configuration:

const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,

  // Tab focus options (all optional)
  checkOnFocus: true,           // Enable focus checking (default: true)
  focusDebounce: 100,           // Debounce rapid focus events (default: 100ms)

  // Focus check callback
  onFocusCheck: (session) => {
    console.log('Tab focused, session state:', session ? 'authenticated' : 'logged out');
  }
});

// Manual focus check (useful for testing)
const currentSession = await sessionManager.checkAuthState();

Example Scenarios:

// Scenario 1: Login in another tab
// Tab A: User is logged out
// Tab B: User logs in successfully
// Tab A: User switches back โ†’ onSessionStarted() fires โ†’ UI updates to authenticated

// Scenario 2: Logout in another tab
// Tab A: User is logged in
// Tab B: User logs out
// Tab A: User switches back โ†’ onSessionExpired() fires โ†’ UI updates to logged out

// Scenario 3: Token refresh in another tab
// Tab A: Token expires in 30 seconds
// Tab B: Token gets refreshed โ†’ now expires in 1 hour
// Tab A: User switches back โ†’ Timer synchronizes to 1 hour expiry

Benefits:

โœ… Instant UI Updates: No delay when switching between tabs โœ… Better UX: Immediate feedback on authentication state changes โœ… Bidirectional Sync: Detects both login and logout from other tabs โœ… Security: Quick detection of session changes from other tabs โœ… Clock Synchronization: Aligns with token refreshes from other tabs โœ… Efficiency: Only checks when user actually focuses the tab

Cross-Domain Authentication

For applications spanning multiple subdomains, use cookie storage with a shared domain:

// On auth.example.com (login page)
const authSessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new CookieStorageAdapter({
    domain: '.example.com',
    secure: true,
    sameSite: 'lax'
  })
});

// After successful login
await authSessionManager.startSession(tokens);

// On app1.example.com and app2.example.com
const appSessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  storage: new CookieStorageAdapter({
    domain: '.example.com',
    secure: true,
    sameSite: 'lax'
  })
});

// Session is automatically available across subdomains
const accessToken = await appSessionManager.getCurrentAccessToken();

Error Handling

const sessionManager = new TokenSessionManager({
  refreshTokenFn: refreshTokens,
  onRefreshError: async (error) => {
    if (error.message.includes('invalid_grant')) {
      // Refresh token is invalid, redirect to login
      window.location.href = '/login';
    } else {
      // Network error, retry later
      console.error('Refresh failed, will retry:', error);
    }
  },
  onSessionError: (error) => {
    console.error('Session error:', error);
  }
});

License

MIT