# Token Optimization & Authentication Enhancement Guide ## Overview This document provides detailed guidance for optimizing Auth0 token management, enhancing error recovery, and implementing robust authentication patterns for improved mobile and desktop experience. ## Current Implementation Analysis ### Existing Token Management Strengths **File**: `/home/egullickson/motovaultpro/frontend/src/core/auth/Auth0Provider.tsx` **Current Features**: - Progressive fallback strategy with 3 retry attempts - Mobile-optimized token acquisition with enhanced timeouts - Exponential backoff for mobile network conditions - Pre-warming token cache for mobile devices - Sophisticated error handling and logging **Current Token Acquisition Logic** (lines 44-95): ```typescript const getTokenWithRetry = async (): Promise => { const maxRetries = 3; const baseDelay = 500; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { let token: string; if (attempt === 1) { // Cache-first approach token = await getAccessTokenSilently({ cacheMode: 'on', timeoutInSeconds: 15, }); } else if (attempt === 2) { // Force refresh token = await getAccessTokenSilently({ cacheMode: 'off', timeoutInSeconds: 20, }); } else { // Final attempt with extended timeout token = await getAccessTokenSilently({ timeoutInSeconds: 30, }); } return token; } catch (error) { const delay = baseDelay * Math.pow(2, attempt - 1); if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, delay)); } } } return null; }; ``` ## Enhancement Areas ### 1. Token Refresh Retry Logic for 401 Responses **Problem**: API calls fail with 401 responses without attempting token refresh **Solution**: Implement automatic token refresh and retry for 401 errors #### Enhanced API Client **File**: `frontend/src/core/api/client.ts` (modifications) ```typescript import { Auth0Context } from '@auth0/auth0-react'; import { useContext } from 'react'; // Enhanced token management service class TokenManager { private static instance: TokenManager; private isRefreshing = false; private failedQueue: Array<{ resolve: (token: string) => void; reject: (error: Error) => void; }> = []; static getInstance(): TokenManager { if (!TokenManager.instance) { TokenManager.instance = new TokenManager(); } return TokenManager.instance; } async refreshToken(getAccessTokenSilently: any): Promise { if (this.isRefreshing) { // Return a promise that will resolve when the current refresh completes return new Promise((resolve, reject) => { this.failedQueue.push({ resolve, reject }); }); } this.isRefreshing = true; try { // Force token refresh const token = await getAccessTokenSilently({ cacheMode: 'off', timeoutInSeconds: 20, }); // Process queued requests this.failedQueue.forEach(({ resolve }) => resolve(token)); this.failedQueue = []; return token; } catch (error) { // Reject queued requests this.failedQueue.forEach(({ reject }) => reject(error as Error)); this.failedQueue = []; throw error; } finally { this.isRefreshing = false; } } } // Enhanced API client with 401 retry logic export const createApiClient = (getAccessTokenSilently: any) => { const tokenManager = TokenManager.getInstance(); const client = axios.create({ baseURL: process.env.REACT_APP_API_URL || '/api', timeout: 10000, headers: { 'Content-Type': 'application/json', }, }); // Request interceptor - inject tokens client.interceptors.request.use( async (config) => { try { const token = await getAccessTokenSilently({ cacheMode: 'on', timeoutInSeconds: 15, }); if (token) { config.headers.Authorization = `Bearer ${token}`; } } catch (error) { console.warn('Token acquisition failed, proceeding without token:', error); } return config; }, (error) => Promise.reject(error) ); // Response interceptor - handle 401s with token refresh retry client.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // Handle 401 responses with token refresh if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { console.log('401 detected, attempting token refresh...'); const newToken = await tokenManager.refreshToken(getAccessTokenSilently); // Update the failed request with new token originalRequest.headers.Authorization = `Bearer ${newToken}`; // Retry the original request return client(originalRequest); } catch (refreshError) { console.error('Token refresh failed:', refreshError); // If token refresh fails, the user needs to re-authenticate // This should trigger the Auth0 login flow window.location.href = '/login'; return Promise.reject(refreshError); } } // Enhanced mobile error handling if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ); if (isMobile) { error.message = 'Connection timeout. Please check your network and try again.'; } } return Promise.reject(error); } ); return client; }; ``` ### 2. Background Token Refresh **Problem**: Tokens can expire during extended mobile use **Solution**: Implement proactive background token refresh #### Background Token Service **File**: `frontend/src/core/auth/backgroundTokenService.ts` (new) ```typescript class BackgroundTokenService { private static instance: BackgroundTokenService; private refreshInterval: NodeJS.Timeout | null = null; private getAccessTokenSilently: any = null; private isActive = false; static getInstance(): BackgroundTokenService { if (!BackgroundTokenService.instance) { BackgroundTokenService.instance = new BackgroundTokenService(); } return BackgroundTokenService.instance; } start(getAccessTokenSilently: any) { if (this.isActive) return; this.getAccessTokenSilently = getAccessTokenSilently; this.isActive = true; // Refresh token every 45 minutes (tokens typically expire after 1 hour) this.refreshInterval = setInterval(() => { this.refreshTokenInBackground(); }, 45 * 60 * 1000); // Also refresh on app visibility change (mobile app switching) document.addEventListener('visibilitychange', this.handleVisibilityChange); } stop() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } document.removeEventListener('visibilitychange', this.handleVisibilityChange); this.isActive = false; } private handleVisibilityChange = () => { if (document.visibilityState === 'visible') { // App became visible, refresh token to ensure it's valid this.refreshTokenInBackground(); } }; private async refreshTokenInBackground() { if (!this.getAccessTokenSilently) return; try { await this.getAccessTokenSilently({ cacheMode: 'off', timeoutInSeconds: 10, }); console.debug('Background token refresh successful'); } catch (error) { console.warn('Background token refresh failed:', error); // Don't throw - this is a background operation } } } export default BackgroundTokenService; ``` #### Integration with Auth0Provider **File**: `/home/egullickson/motovaultpro/frontend/src/core/auth/Auth0Provider.tsx` (modifications) ```typescript import BackgroundTokenService from './backgroundTokenService'; // Inside the Auth0Provider component const CustomAuth0Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { const initializeAuth = async () => { // Existing initialization logic... // Start background token service after authentication if (isAuthenticated) { const backgroundService = BackgroundTokenService.getInstance(); backgroundService.start(getAccessTokenSilently); } }; initializeAuth(); // Cleanup on unmount return () => { const backgroundService = BackgroundTokenService.getInstance(); backgroundService.stop(); }; }, [isAuthenticated, getAccessTokenSilently]); // Rest of component... }; ``` ### 3. Enhanced Error Boundaries for Token Failures **Problem**: Token acquisition failures can break the app **Solution**: Implement error boundaries with graceful degradation #### Auth Error Boundary **File**: `frontend/src/core/auth/AuthErrorBoundary.tsx` (new) ```typescript import React, { Component, ErrorInfo, ReactNode } from 'react'; interface Props { children: ReactNode; fallback?: ReactNode; } interface State { hasError: boolean; error: Error | null; isAuthError: boolean; } export class AuthErrorBoundary extends Component { public state: State = { hasError: false, error: null, isAuthError: false, }; public static getDerivedStateFromError(error: Error): State { const isAuthError = error.message.includes('auth') || error.message.includes('token') || error.message.includes('login'); return { hasError: true, error, isAuthError }; } public componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('Auth Error Boundary caught an error:', error, errorInfo); } private handleRetry = () => { this.setState({ hasError: false, error: null, isAuthError: false }); }; private handleReauth = () => { // Redirect to login window.location.href = '/login'; }; public render() { if (this.state.hasError) { if (this.props.fallback) { return this.props.fallback; } return (

{this.state.isAuthError ? 'Authentication Error' : 'Something went wrong'}

{this.state.isAuthError ? 'There was a problem with authentication. Please sign in again.' : 'An unexpected error occurred. Please try again.'}

{this.state.isAuthError && ( )}
{process.env.NODE_ENV === 'development' && this.state.error && (
Error Details (dev only)
                  {this.state.error.message}
                
)}
); } return this.props.children; } } ``` ### 4. Optimized Mobile Token Warm-up **Problem**: Current 100ms delay may not be sufficient for all mobile devices **Solution**: Adaptive warm-up timing based on device performance #### Adaptive Token Warm-up **File**: `frontend/src/core/auth/tokenWarmup.ts` (new) ```typescript class TokenWarmupService { private static instance: TokenWarmupService; private warmupDelay: number = 100; // Default static getInstance(): TokenWarmupService { if (!TokenWarmupService.instance) { TokenWarmupService.instance = new TokenWarmupService(); } return TokenWarmupService.instance; } async calculateOptimalDelay(): Promise { // Detect device performance characteristics const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ); if (!isMobile) { return 50; // Faster for desktop } // Mobile performance detection const startTime = performance.now(); // Simple CPU-bound task to gauge performance let sum = 0; for (let i = 0; i < 100000; i++) { sum += Math.random(); } const endTime = performance.now(); const executionTime = endTime - startTime; // Adaptive delay based on device performance if (executionTime < 10) { return 100; // Fast mobile device } else if (executionTime < 50) { return 200; // Medium mobile device } else { return 500; // Slower mobile device } } async warmupWithAdaptiveDelay(callback: () => Promise): Promise { const delay = await this.calculateOptimalDelay(); this.warmupDelay = delay; return new Promise((resolve) => { setTimeout(async () => { await callback(); resolve(); }, delay); }); } getLastWarmupDelay(): number { return this.warmupDelay; } } export default TokenWarmupService; ``` #### Integration with Auth0Provider ```typescript // Inside Auth0Provider initialization const warmupService = TokenWarmupService.getInstance(); await warmupService.warmupWithAdaptiveDelay(async () => { try { await getAccessTokenSilently({ cacheMode: 'on', timeoutInSeconds: 5, }); } catch (error) { // Warm-up failed, but continue initialization console.warn('Token warm-up failed:', error); } }); ``` ### 5. Offline Token Management **Problem**: Mobile users may have intermittent connectivity **Solution**: Implement offline token caching and validation #### Offline Token Cache **File**: `frontend/src/core/auth/offlineTokenCache.ts` (new) ```typescript interface CachedTokenInfo { token: string; expiresAt: number; cachedAt: number; } class OfflineTokenCache { private static instance: OfflineTokenCache; private readonly CACHE_KEY = 'motovaultpro-offline-token'; private readonly MAX_OFFLINE_DURATION = 30 * 60 * 1000; // 30 minutes static getInstance(): OfflineTokenCache { if (!OfflineTokenCache.instance) { OfflineTokenCache.instance = new OfflineTokenCache(); } return OfflineTokenCache.instance; } cacheToken(token: string): void { try { // Decode JWT to get expiration (simplified - in production, use a JWT library) const payload = JSON.parse(atob(token.split('.')[1])); const expiresAt = payload.exp * 1000; // Convert to milliseconds const tokenInfo: CachedTokenInfo = { token, expiresAt, cachedAt: Date.now(), }; localStorage.setItem(this.CACHE_KEY, JSON.stringify(tokenInfo)); } catch (error) { console.warn('Failed to cache token:', error); } } getCachedToken(): string | null { try { const cached = localStorage.getItem(this.CACHE_KEY); if (!cached) return null; const tokenInfo: CachedTokenInfo = JSON.parse(cached); const now = Date.now(); // Check if token is expired if (now >= tokenInfo.expiresAt) { this.clearCache(); return null; } // Check if we've been offline too long if (now - tokenInfo.cachedAt > this.MAX_OFFLINE_DURATION) { this.clearCache(); return null; } return tokenInfo.token; } catch (error) { console.warn('Failed to retrieve cached token:', error); this.clearCache(); return null; } } clearCache(): void { localStorage.removeItem(this.CACHE_KEY); } isOnline(): boolean { return navigator.onLine; } } export default OfflineTokenCache; ``` ## Implementation Integration ### Updated API Client Factory **File**: `frontend/src/core/api/index.ts` (new) ```typescript import { createApiClient } from './client'; import OfflineTokenCache from '../auth/offlineTokenCache'; export const createEnhancedApiClient = (getAccessTokenSilently: any) => { const offlineCache = OfflineTokenCache.getInstance(); const client = createApiClient(getAccessTokenSilently); // Enhance request interceptor for offline support client.interceptors.request.use( async (config) => { try { // Try to get fresh token const token = await getAccessTokenSilently({ cacheMode: 'on', timeoutInSeconds: 15, }); if (token) { // Cache token for offline use offlineCache.cacheToken(token); config.headers.Authorization = `Bearer ${token}`; } } catch (error) { // If online token acquisition fails, try cached token if (!offlineCache.isOnline()) { const cachedToken = offlineCache.getCachedToken(); if (cachedToken) { config.headers.Authorization = `Bearer ${cachedToken}`; console.log('Using cached token for offline request'); } } } return config; }, (error) => Promise.reject(error) ); return client; }; ``` ## Testing Requirements ### Token Management Tests - ✅ 401 responses trigger automatic token refresh and retry - ✅ Background token refresh prevents expiration during extended use - ✅ Token warm-up adapts to device performance - ✅ Error boundaries handle token failures gracefully - ✅ Offline token caching works during network interruptions ### Mobile-Specific Tests - ✅ Enhanced retry logic handles poor mobile connectivity - ✅ App visibility changes trigger token refresh - ✅ Mobile error messages are user-friendly - ✅ Token acquisition timing adapts to device performance ### Integration Tests - ✅ Enhanced API client works with existing components - ✅ Background service doesn't interfere with normal token acquisition - ✅ Error boundaries don't break existing error handling - ✅ Offline caching doesn't conflict with Auth0's built-in caching ## Implementation Phases ### Phase 1: Core Enhancements 1. Implement 401 retry logic in API client 2. Add background token refresh service 3. Create auth error boundary ### Phase 2: Mobile Optimizations 1. Implement adaptive token warm-up 2. Add offline token caching 3. Enhance mobile error handling ### Phase 3: Integration & Testing 1. Integrate all enhancements with existing Auth0Provider 2. Test across various network conditions 3. Validate mobile and desktop compatibility ### Phase 4: Monitoring & Analytics 1. Add token performance monitoring 2. Implement retry success/failure analytics 3. Add offline usage tracking ## Success Criteria Upon completion: 1. **Robust Token Management**: No 401 failures without retry attempts 2. **Background Refresh**: No token expiration issues during extended use 3. **Mobile Optimization**: Adaptive timing and offline support for mobile users 4. **Error Recovery**: Graceful handling of all token acquisition failures 5. **Performance**: Minimal impact on app performance and user experience These enhancements will provide a robust, mobile-optimized authentication system that gracefully handles network issues and provides an excellent user experience across all platforms.