Files
retail/docs/AUTH_TROUBLESHOOTING.md
Phuoc Nguyen f6d2971224 fix md
2025-10-13 17:49:35 +07:00

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:

  1. Verify AuthWrapper uses ref.watch(authProvider) not ref.read()
  2. Check auth provider has @Riverpod(keepAlive: true) annotation
  3. Verify login method explicitly sets isAuthenticated: true in state
  4. 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 in app.dart
  • Verify app.dart has initState() that calls auth.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/profile response missing createdAt field
  • 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 Authorization header

Solution:

  1. Verify DioClient.setAuthToken() is called after login
  2. Check DioClient has interceptor that adds Authorization header
  3. Look for log: Token set in DioClient
  4. 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:

  1. Check Provider State:

    final authState = ref.read(authProvider);
    print('isAuthenticated: ${authState.isAuthenticated}');
    print('user: ${authState.user?.name}');
    print('errorMessage: ${authState.errorMessage}');
    
  2. Check Token Storage:

    final storage = SecureStorage();
    final hasToken = await storage.hasAccessToken();
    print('Has token: $hasToken');
    
  3. Check Backend:

    curl -X POST http://localhost:3000/api/auth/login \
      -H "Content-Type: application/json" \
      -d '{"email":"test@retailpos.com","password":"Test123!"}'
    
  4. 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)

  1. Clear app data or use fresh install
  2. Run app: flutter run
  3. Expected: Shows LoginPage immediately
  4. Result: Pass / Fail

Test 2: Login Flow

  1. Start at LoginPage
  2. Enter credentials: admin@retailpos.com / Admin123!
  3. Tap Login
  4. Expected:
    • Loading indicator appears
    • On success: Navigate to MainScreen with bottom tabs
  5. Result: Pass / Fail

Test 3: Token Persistence

  1. Login successfully
  2. Close app completely
  3. Restart app
  4. Expected:
    • Shows loading briefly
    • Automatically goes to MainScreen (no login needed)
  5. Result: Pass / Fail

Test 4: Logout Flow

  1. While logged in, go to Settings tab
  2. Tap Logout button
  3. Confirm logout
  4. Expected: Navigate back to LoginPage
  5. Result: Pass / Fail

Test 5: Invalid Credentials

  1. Enter wrong email/password
  2. Tap Login
  3. Expected:
    • Shows error SnackBar
    • Stays on LoginPage
    • Error message displayed
  4. 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 copyWith method with clearUser and clearError flags
    • Updated login/register to explicitly set isAuthenticated: true
    • Moved auth check to initialize() method
  • lib/app.dart

    • Changed from ConsumerWidget to ConsumerStatefulWidget
    • Added initState() to call auth.initialize()
  • 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)
  • lib/features/auth/presentation/pages/login_page.dart

    • Already correct - login logic properly calls provider

Expected Behavior After Fixes

  1. App starts → auth initializes → shows LoginPage (if no token)
  2. Login success → state updates → AuthWrapper rebuilds → shows MainScreen
  3. Token persists → app restart → auto-login works
  4. Logout → state clears → AuthWrapper rebuilds → shows LoginPage
  5. All tabs accessible after login (Home, Products, Categories, Settings)

Next Steps If Still Not Working

  1. Add Debug Logs: Add print statements to trace state changes
  2. Check Backend: Ensure API endpoints are working and returning correct data
  3. Verify Token Format: Check that JWT token is valid format
  4. Check API Response Structure: Ensure response matches model expectations
  5. Test with Hot Restart: Try r (hot reload) vs R (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.