12 KiB
Authentication Troubleshooting Guide
Date: October 10, 2025
This guide helps debug authentication issues in the Retail POS app.
For implementation details, see: AUTH_IMPLEMENTATION_SUMMARY.md For quick start, see: AUTH_READY.md
Common Issues
Issue 1: Login Successful But No Navigation
Symptoms:
- Login API call succeeds
- Token is saved
- But app doesn't navigate to MainScreen
- AuthWrapper doesn't react to state change
Root Cause: State not updating properly or UI not watching state
Solution:
- Verify
AuthWrapperusesref.watch(authProvider)notref.read() - Check auth provider has
@Riverpod(keepAlive: true)annotation - Verify login method explicitly sets
isAuthenticated: truein state - Check logs for successful state update
Issue 2: Auto-Login Not Working
Symptoms:
- Login with Remember Me checked
- Close and reopen app
- Shows login page instead of auto-login
Common Causes:
A. Remember Me Not Enabled
- Check the Remember Me checkbox was actually checked during login
- Look for log:
Token saved to secure storage (persistent) - If you see
Token NOT saved (session only), checkbox was not checked
B. Token Not Being Loaded on Startup
- Check logs for:
Initializing auth state... - If missing,
initialize()is not being called inapp.dart - Verify
app.darthasinitState()that callsauth.initialize()
C. Profile API Failing
- Token loads but profile fetch fails
- Check logs for:
Failed to get profile: [error] - Common causes: Token expired, backend not running, network error
- Solution: Ensure backend is running and token is valid
D. UserModel Parsing Error
- Error:
type 'Null' is not a subtype of type 'String' in type cast - Cause: Backend
/auth/profileresponse missingcreatedAtfield - Solution: Already fixed - UserModel now handles optional
createdAt
Issue 3: Token Not Added to API Requests
Symptoms:
- Login successful
- But subsequent API calls return 401 Unauthorized
- API requests missing
Authorizationheader
Solution:
- Verify
DioClient.setAuthToken()is called after login - Check
DioClienthas interceptor that addsAuthorizationheader - Look for log:
Token set in DioClient - Verify dio interceptor:
options.headers['Authorization'] = 'Bearer $_authToken'
Issue 4: "Connection Refused" Error
Symptoms:
- Login fails immediately
- Error: Connection refused or network error
Solution:
- Ensure backend is running at
http://localhost:3000 - Check API endpoint URL in
lib/core/constants/api_constants.dart - Verify backend CORS is configured (if running on web)
- Test backend directly:
curl http://localhost:3000/api/auth/login
Issue 5: Invalid Credentials Error Even with Correct Password
Symptoms:
- Entering correct credentials
- Always getting "Invalid email or password"
Solution:
- Verify user exists in backend database
- Check backend logs for authentication errors
- Test login directly with curl or Postman
- Verify email and password match backend user
How Auth Flow Should Work
1. App Startup
main()
→ ProviderScope created
→ RetailApp builds
→ initState() schedules auth initialization
→ auth.initialize() checks for saved token
→ If token found: loads user profile, sets isAuthenticated = true
→ If no token: sets isAuthenticated = false
2. Login Flow
User enters credentials
→ Taps Login button
→ _handleLogin() called
→ ref.read(authProvider.notifier).login(email, password)
→ API call to /api/auth/login
→ Success: saves token, sets user, sets isAuthenticated = true
→ AuthWrapper watches authProvider
→ isAuthenticated changes to true
→ AuthWrapper rebuilds
→ Shows MainScreen instead of LoginPage
3. Logout Flow
User taps Logout in Settings
→ Confirmation dialog shown
→ ref.read(authProvider.notifier).logout()
→ Token cleared from secure storage
→ DioClient token cleared
→ State set to isAuthenticated = false
→ AuthWrapper rebuilds
→ Shows LoginPage
Debug Tools
Enable Debug Logging
The auth system has extensive logging. Look for these key logs:
Login Flow:
🔐 Repository: Starting login (rememberMe: true/false)...
💾 SecureStorage: Token saved successfully
✅ Login SUCCESS: user=Name, token length=XXX
Auto-Login Flow:
🚀 Initializing auth state...
🔍 Has token in storage: true/false
🚀 Token found, fetching user profile...
✅ Profile loaded: Name
Common Error Logs:
❌ No token found in storage
❌ Failed to get profile: [error message]
❌ Login failed: [error message]
Debug Checklist
If auth flow still not working:
-
Check Provider State:
final authState = ref.read(authProvider); print('isAuthenticated: ${authState.isAuthenticated}'); print('user: ${authState.user?.name}'); print('errorMessage: ${authState.errorMessage}'); -
Check Token Storage:
final storage = SecureStorage(); final hasToken = await storage.hasAccessToken(); print('Has token: $hasToken'); -
Check Backend:
curl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"test@retailpos.com","password":"Test123!"}' -
Check Logs:
- Watch for errors in Flutter console
- Check backend logs for API errors
- Look for network errors or timeouts
Testing the Fix
Test 1: Fresh App Start (No Token)
- Clear app data or use fresh install
- Run app:
flutter run - Expected: Shows LoginPage immediately
- Result: ✅ Pass / ❌ Fail
Test 2: Login Flow
- Start at LoginPage
- Enter credentials: admin@retailpos.com / Admin123!
- Tap Login
- Expected:
- Loading indicator appears
- On success: Navigate to MainScreen with bottom tabs
- Result: ✅ Pass / ❌ Fail
Test 3: Token Persistence
- Login successfully
- Close app completely
- Restart app
- Expected:
- Shows loading briefly
- Automatically goes to MainScreen (no login needed)
- Result: ✅ Pass / ❌ Fail
Test 4: Logout Flow
- While logged in, go to Settings tab
- Tap Logout button
- Confirm logout
- Expected: Navigate back to LoginPage
- Result: ✅ Pass / ❌ Fail
Test 5: Invalid Credentials
- Enter wrong email/password
- Tap Login
- Expected:
- Shows error SnackBar
- Stays on LoginPage
- Error message displayed
- Result: ✅ Pass / ❌ Fail
Architecture Diagram
┌─────────────────────────────────────────────────┐
│ ProviderScope │
│ ┌───────────────────────────────────────────┐ │
│ │ RetailApp │ │
│ │ (initializes auth on startup) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ MaterialApp │ │ │
│ │ │ ┌───────────────────────────────┐ │ │ │
│ │ │ │ AuthWrapper │ │ │ │
│ │ │ │ (watches authProvider) │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ if isAuthenticated: │ │ │ │
│ │ │ │ ┌─────────────────────────┐ │ │ │ │
│ │ │ │ │ MainScreen │ │ │ │ │
│ │ │ │ │ (with bottom tabs) │ │ │ │ │
│ │ │ │ └─────────────────────────┘ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ else: │ │ │ │
│ │ │ │ ┌─────────────────────────┐ │ │ │ │
│ │ │ │ │ LoginPage │ │ │ │ │
│ │ │ │ │ (login form) │ │ │ │ │
│ │ │ │ └─────────────────────────┘ │ │ │ │
│ │ │ └───────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
↕
┌───────────────┐
│ authProvider │
│ (keepAlive) │
└───────────────┘
↕
┌───────────────────────┐
│ authRepository │
│ ↓ │
│ authRemoteDataSource │
│ ↓ │
│ dioClient │
│ ↓ │
│ secureStorage │
└───────────────────────┘
Files Modified
Core Auth Files
-
✅
lib/features/auth/presentation/providers/auth_provider.dart- Added
@Riverpod(keepAlive: true)to Auth provider - Fixed
copyWithmethod withclearUserandclearErrorflags - Updated login/register to explicitly set
isAuthenticated: true - Moved auth check to
initialize()method
- Added
-
✅
lib/app.dart- Changed from
ConsumerWidgettoConsumerStatefulWidget - Added
initState()to callauth.initialize()
- Changed from
-
✅
lib/main.dart- Removed GetIt initialization
- Using pure Riverpod for DI
-
✅
lib/features/auth/presentation/widgets/auth_wrapper.dart- Already correct - uses
ref.watch(authProvider)
- Already correct - uses
-
✅
lib/features/auth/presentation/pages/login_page.dart- Already correct - login logic properly calls provider
Expected Behavior After Fixes
- ✅ App starts → auth initializes → shows LoginPage (if no token)
- ✅ Login success → state updates → AuthWrapper rebuilds → shows MainScreen
- ✅ Token persists → app restart → auto-login works
- ✅ Logout → state clears → AuthWrapper rebuilds → shows LoginPage
- ✅ All tabs accessible after login (Home, Products, Categories, Settings)
Next Steps If Still Not Working
- Add Debug Logs: Add print statements to trace state changes
- Check Backend: Ensure API endpoints are working and returning correct data
- Verify Token Format: Check that JWT token is valid format
- Check API Response Structure: Ensure response matches model expectations
- Test with Hot Restart: Try
r(hot reload) vsR(hot restart) in Flutter
Status: All known issues fixed. Auth flow should work correctly now.
If issues persist, add debug logging as described above to trace the exact point of failure.