Login Flow Documentation¶
Overview¶
This document describes the simplified login flow using the User Settings API integration for authentication and settings synchronization.
User Settings API Integration¶
The login flow has been completely redesigned around the User Settings API to provide cross-device sync and simplified data management.
Core Principle¶
After login, make ONE API call (User Settings API), which returns both user settings and all organizations/projects data. Navigate based on current_organization_id from the API response.
Login Flow Steps¶
1. User Login¶
- User enters credentials and submits login form
AuthContext.login()is called with access token, refresh token, and user data
2. Clear Previous Session Data¶
- All cached data is cleared from localStorage using
clearSessionData() - This includes: projects, organizations, notebooks, tools, credentials, etc.
- Only auth tokens are preserved
3. Store Authentication Data¶
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
localStorage.setItem('user', JSON.stringify(user));
setIsLoggedIn(true);
4. Fetch User Settings API¶
- Call
/user_settings?client=webAPI endpoint - This is the ONLY API call during login
- Returns user settings AND all organizations/projects data in one response
- No additional calls to
fetchOrganizationsorfetchProjects
5. Apply Settings and Store Data¶
const settings = await UserSettingsService.loadFromAPI('web');
// Apply user settings
if (settings.current_organization_id) {
localStorage.setItem('currentOrganizationId', settings.current_organization_id);
}
if (settings.theme) {
setIsDarkMode(settings.theme === 'dark');
localStorage.setItem('darkMode', settings.theme === 'dark');
}
if (settings.language) {
i18n.changeLanguage(settings.language);
}
// Transform and store organizations data for backward compatibility
const organizationsData = transformSidebarToOrganizations(settings.sidebar);
localStorage.setItem('organizations', JSON.stringify(organizationsData));
6. Simple Navigation Decision¶
Scenario A: User Has Current Organization¶
Condition: settings.current_organization_id exists
if (settings.current_organization_id) {
console.log('Login successful, navigating to dashboard');
navigate('/admin/dashboard', { replace: true });
}
Scenario B: User Has Organizations But No Current One¶
Condition: settings.sidebar.organizations.length > 0 but no current_organization_id
if (settings.sidebar.organizations.length > 0) {
// Set first organization as current
const firstOrg = settings.sidebar.organizations[0];
localStorage.setItem('currentOrganizationId', firstOrg.id);
navigate('/admin/dashboard', { replace: true });
}
Scenario C: User Has No Organizations¶
Condition: settings.sidebar.organizations.length === 0
if (settings.sidebar.organizations.length === 0) {
console.log('Login successful, navigating to organizations page');
navigate('/admin/organizations', { replace: true });
}
What Changed from Previous Implementation¶
Removed Complexity¶
- ❌ Priority-based navigation (Priority 1, 2, 3, 4)
- ❌ Project-organization mapping during login
- ❌ Multiple API calls (fetchOrganizations, fetchOrganizationDetails, fetchProjects)
- ❌ Onboarding redirect logic (onboarding_completed, onboarding_skipped)
- ❌ Complex data transformation (JSON:API relationships and included data)
- ❌ Cached data navigation paths
- ❌ Complex logging with emojis and prefixes
Added Simplicity¶
- ✅ Single API call (User Settings API only)
- ✅ Cross-device sync (settings synchronized automatically)
- ✅ Direct sidebar rendering (no data transformation needed)
- ✅ Built-in permissions (meta.can structure included)
- ✅ Predictable behavior for all users
- ✅ Simple logging with clear messages
Dashboard Responsibilities¶
The Dashboard component now handles its own data loading:
const Dashboard = () => {
useEffect(() => {
// Read organizations from localStorage (already loaded during login)
const orgsData = localStorage.getItem('organizations');
if (orgsData) {
const parsed = JSON.parse(orgsData);
setOrganizations(parsed.data || []);
}
// Load additional data as needed (wallets, projects, etc.)
// Each component is responsible for its own data
}, []);
};
Navigation Logic¶
The navigation logic has been simplified to use the User Settings API response:
// In AuthContext.login()
const settings = await UserSettingsService.loadFromAPI('web');
if (settings?.current_organization_id) {
// User has a current organization - go to dashboard
navigate('/admin/dashboard', { replace: true });
} else if (settings?.sidebar?.organizations?.length > 0) {
// User has organizations but no current one - set first as current
const firstOrg = settings.sidebar.organizations[0];
localStorage.setItem('currentOrganizationId', firstOrg.id);
navigate('/admin/dashboard', { replace: true });
} else {
// User has no organizations - go to organizations page
navigate('/admin/organizations', { replace: true });
}
Error Handling¶
Login Errors¶
try {
const settings = await UserSettingsService.loadFromAPI('web');
if (settings) {
// Apply settings and navigate
} else {
// Fall back to old fetchOrganizations flow
const res = await fetchOrganizations();
// ... fallback logic
}
} catch (error) {
console.error('Login failed:', error);
setError(t('errors.loginFailed', 'Failed to load user settings. Please try again.'));
// User stays on login page and can retry
}
Logging Strategy¶
Simple, Clear Messages¶
"Login successful, navigating to dashboard""Login successful, navigating to onboarding""Login failed:"followed by error details
No Complex Patterns¶
- No emoji prefixes (🔍, ✅, 🎯)
- No component prefixes ("AuthContext:")
- No verbose debug information
Summary Table¶
| Condition | Current Org ID | Organizations | Navigate To | Additional API Calls |
|---|---|---|---|---|
| Has current org | Set | ≥ 1 | /admin/dashboard |
None during login |
| No current org | Null | ≥ 1 | /admin/dashboard (set first org) |
None during login |
| No organizations | Null | 0 | /admin/organizations |
None during login |
Performance Improvements¶
Before (Complex Flow)¶
- API Calls: 2-3 (fetchOrganizations, fetchOrganizationDetails, sometimes fetchProjects)
- Data Transformation: Complex JSON:API relationship matching
- Navigation Logic: ~600 lines
- localStorage Operations: 20+
- Time to Dashboard: ~3-5 seconds
After (User Settings API Flow)¶
- API Calls: 1 (User Settings API only)
- Data Transformation: None - direct sidebar rendering
- Navigation Logic: ~50 lines
- localStorage Operations: 5-10
- Time to Dashboard: ~1-2 seconds
- Cross-Device Sync: Settings synchronized automatically
Benefits¶
- Simplicity: 90% reduction in code complexity
- Performance: 50% faster login (1-2 seconds vs 3-5 seconds)
- Cross-Device Sync: Settings synchronized across all devices
- Direct Rendering: No data transformation needed for sidebar
- Built-in Permissions: UI elements show/hide based on API permissions
- Maintainability: Easy to understand and debug
- No Race Conditions: Single source of truth
- Consistent Behavior: Same experience for all users
Notes¶
- Users land on dashboard if they have a current organization
- Previous context (current_organization_id) is restored from User Settings API
- Settings (theme, language, sidebar state) are synchronized across devices
- Individual pages are responsible for loading their own data
- Replace navigation (
{ replace: true }) is used to prevent back button issues - Onboarding flow has been removed - users go directly to organizations page if they have none
Mirrored from traylinx-web:docs/user-manuals/login_flow.md. Edit the source in the traylinx-web repo — changes here are overwritten by the sync script.