Authentication Feature
Complete authentication implementation following clean architecture principles for the warehouse management app.
Architecture Overview
auth/
├── data/ # Data layer
│ ├── datasources/ # API and local data sources
│ │ └── auth_remote_datasource.dart
│ ├── models/ # Data transfer objects
│ │ ├── login_request_model.dart
│ │ └── user_model.dart
│ ├── repositories/ # Repository implementations
│ │ └── auth_repository_impl.dart
│ └── data.dart # Barrel export
│
├── domain/ # Domain layer (business logic)
│ ├── entities/ # Business entities
│ │ └── user_entity.dart
│ ├── repositories/ # Repository interfaces
│ │ └── auth_repository.dart
│ ├── usecases/ # Use cases
│ │ └── login_usecase.dart
│ └── domain.dart # Barrel export
│
├── presentation/ # Presentation layer (UI)
│ ├── pages/ # Screen widgets
│ │ └── login_page.dart
│ ├── providers/ # State management
│ │ └── auth_provider.dart
│ ├── widgets/ # Reusable widgets
│ │ └── login_form.dart
│ └── presentation.dart # Barrel export
│
├── di/ # Dependency injection
│ └── auth_dependency_injection.dart
│
├── auth.dart # Main barrel export
└── README.md # This file
Features
Implemented
- ✅ User login with username/password
- ✅ Token storage in secure storage
- ✅ Authentication state management
- ✅ Form validation
- ✅ Error handling with user-friendly messages
- ✅ Loading states
- ✅ Auto-navigation after successful login
- ✅ Check authentication status on app start
- ✅ Logout functionality
- ✅ Token refresh (prepared for future use)
Pending
- ⏳ Integration with actual API endpoints
- ⏳ Biometric authentication
- ⏳ Remember me functionality
- ⏳ Password recovery
Data Flow
Login Flow
1. User enters credentials in LoginPage
2. LoginForm validates input
3. AuthNotifier.login() is called
4. LoginUseCase validates and processes request
5. AuthRepository calls AuthRemoteDataSource
6. API response is converted to UserModel
7. Tokens saved to SecureStorage
8. AuthState updated to authenticated
9. Navigation to warehouses page
Logout Flow
1. User triggers logout
2. AuthNotifier.logout() is called
3. LogoutUseCase calls AuthRepository
4. API logout call (optional, can fail)
5. SecureStorage cleared
6. AuthState reset to initial
7. Navigation to login page
Usage
Basic Import
import 'package:minhthu/features/auth/auth.dart';
Using in UI
// In a ConsumerWidget or ConsumerStatefulWidget
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch auth state
final authState = ref.watch(authProvider);
// Check authentication
if (authState.isAuthenticated) {
return AuthenticatedView(user: authState.user!);
}
// Handle loading
if (authState.isLoading) {
return LoadingIndicator();
}
// Show error
if (authState.error != null) {
return ErrorView(message: authState.error!);
}
return LoginView();
}
}
Perform Login
// In your widget
void handleLogin(String username, String password) {
ref.read(authProvider.notifier).login(username, password);
}
Perform Logout
void handleLogout() {
ref.read(authProvider.notifier).logout();
}
Check Auth Status
void checkIfAuthenticated() async {
await ref.read(authProvider.notifier).checkAuthStatus();
}
Listen to Auth Changes
ref.listen(authProvider, (previous, next) {
if (next.isAuthenticated) {
// Navigate to home
context.go('/home');
} else if (next.error != null) {
// Show error snackbar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(next.error!)),
);
}
});
API Integration
Expected API Response Format
{
"Value": {
"userId": "string",
"username": "string",
"accessToken": "string",
"refreshToken": "string"
},
"IsSuccess": true,
"IsFailure": false,
"Errors": [],
"ErrorCodes": []
}
Login Request
POST /api/v1/auth/login
{
"username": "string",
"password": "string"
}
Logout Request
POST /api/v1/auth/logout
Authorization: Bearer {accessToken}
Refresh Token Request
POST /api/v1/auth/refresh
{
"refreshToken": "string"
}
State Management
AuthState
class AuthState {
final UserEntity? user; // Current user or null
final bool isAuthenticated; // Authentication status
final bool isLoading; // Loading indicator
final String? error; // Error message
}
State Transitions
Initial State → Loading → Authenticated (success)
→ Error (failure)
Testing
Unit Tests (TODO)
// Test use cases
test('login with valid credentials returns user', () async {
// Arrange
final useCase = LoginUseCase(mockRepository);
final request = LoginRequestModel(
username: 'testuser',
password: 'password123',
);
// Act
final result = await useCase(request);
// Assert
expect(result.isRight(), true);
});
// Test repository
test('repository saves tokens on successful login', () async {
// Test implementation
});
Widget Tests (TODO)
testWidgets('login page shows form fields', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(home: LoginPage()),
),
);
expect(find.byType(TextField), findsNWidgets(2));
expect(find.text('Username'), findsOneWidget);
expect(find.text('Password'), findsOneWidget);
});
Error Handling
Validation Errors
- Empty username/password
- Username too short (< 3 characters)
- Password too short (< 6 characters)
Network Errors
- Connection timeout
- No internet connection
- Server unreachable
Authentication Errors
- Invalid credentials
- Account locked
- Token expired
Display Errors
All errors are displayed in a user-friendly format in the UI with appropriate styling.
Security Considerations
Implemented
- ✅ Tokens stored in secure storage (encrypted)
- ✅ Password field obscured
- ✅ Auth token added to API headers automatically
- ✅ Token cleared on logout
- ✅ No sensitive data logged
Best Practices
- Never log passwords or tokens
- Use HTTPS for all API calls
- Implement token refresh before expiration
- Clear sensitive data on logout
- Validate all user input
Dependencies
Core Dependencies
flutter_riverpod- State managementdartz- Functional programming (Either type)flutter_secure_storage- Secure token storagedio- HTTP client (via ApiClient)equatable- Value equalitygo_router- Navigation
Internal Dependencies
core/network/api_client.dart- HTTP client wrappercore/storage/secure_storage.dart- Secure storage wrappercore/errors/failures.dart- Error typescore/errors/exceptions.dart- Exception typescore/widgets/custom_button.dart- Button widgetcore/widgets/loading_indicator.dart- Loading widget
Troubleshooting
Common Issues
Issue: Login always fails
- Check API endpoint configuration in
api_endpoints.dart - Verify API is running and accessible
- Check network connectivity
- Verify request/response format matches API
Issue: Tokens not persisted
- Verify secure storage is initialized
- Check device storage permissions
- Clear app data and try again
Issue: Navigation doesn't work after login
- Verify router configuration includes
/warehousesroute - Check if listener in LoginPage is properly set up
- Ensure ProviderScope wraps the app
Issue: State not updating in UI
- Ensure using ConsumerWidget or ConsumerStatefulWidget
- Verify provider is being watched, not just read
- Check if state is properly copied in copyWith
Future Enhancements
Planned Features
-
Biometric Authentication
- Face ID / Touch ID support
- Fallback to password
-
Token Auto-Refresh
- Background token refresh
- Seamless reauthentication
-
Multi-factor Authentication
- OTP support
- SMS verification
-
Remember Me
- Optional persistent login
- Secure device storage
-
Password Reset
- Email-based reset flow
- Security questions
Contributing
When modifying this feature:
- Follow clean architecture principles
- Maintain separation of concerns (data/domain/presentation)
- Add tests for new functionality
- Update this README with changes
- Follow existing code style and patterns
Related Files
- App Router:
lib/core/routing/app_router.dart - API Endpoints:
lib/core/constants/api_endpoints.dart - App Theme:
lib/core/theme/app_theme.dart - Main App:
lib/main.dart