351 lines
12 KiB
Markdown
351 lines
12 KiB
Markdown
# 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](AUTH_IMPLEMENTATION_SUMMARY.md)
|
|
**For quick start, see:** [AUTH_READY.md](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:**
|
|
```dart
|
|
final authState = ref.read(authProvider);
|
|
print('isAuthenticated: ${authState.isAuthenticated}');
|
|
print('user: ${authState.user?.name}');
|
|
print('errorMessage: ${authState.errorMessage}');
|
|
```
|
|
|
|
2. **Check Token Storage:**
|
|
```dart
|
|
final storage = SecureStorage();
|
|
final hasToken = await storage.hasAccessToken();
|
|
print('Has token: $hasToken');
|
|
```
|
|
|
|
3. **Check Backend:**
|
|
```bash
|
|
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
|
|
5. **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.
|