381 lines
9.1 KiB
Markdown
381 lines
9.1 KiB
Markdown
# 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
|
|
```dart
|
|
import 'package:minhthu/features/auth/auth.dart';
|
|
```
|
|
|
|
### Using in UI
|
|
```dart
|
|
// 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
|
|
```dart
|
|
// In your widget
|
|
void handleLogin(String username, String password) {
|
|
ref.read(authProvider.notifier).login(username, password);
|
|
}
|
|
```
|
|
|
|
### Perform Logout
|
|
```dart
|
|
void handleLogout() {
|
|
ref.read(authProvider.notifier).logout();
|
|
}
|
|
```
|
|
|
|
### Check Auth Status
|
|
```dart
|
|
void checkIfAuthenticated() async {
|
|
await ref.read(authProvider.notifier).checkAuthStatus();
|
|
}
|
|
```
|
|
|
|
### Listen to Auth Changes
|
|
```dart
|
|
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
|
|
```json
|
|
{
|
|
"Value": {
|
|
"userId": "string",
|
|
"username": "string",
|
|
"accessToken": "string",
|
|
"refreshToken": "string"
|
|
},
|
|
"IsSuccess": true,
|
|
"IsFailure": false,
|
|
"Errors": [],
|
|
"ErrorCodes": []
|
|
}
|
|
```
|
|
|
|
### Login Request
|
|
```json
|
|
POST /api/v1/auth/login
|
|
{
|
|
"username": "string",
|
|
"password": "string"
|
|
}
|
|
```
|
|
|
|
### Logout Request
|
|
```json
|
|
POST /api/v1/auth/logout
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
### Refresh Token Request
|
|
```json
|
|
POST /api/v1/auth/refresh
|
|
{
|
|
"refreshToken": "string"
|
|
}
|
|
```
|
|
|
|
## State Management
|
|
|
|
### AuthState
|
|
```dart
|
|
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)
|
|
```dart
|
|
// 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)
|
|
```dart
|
|
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 management
|
|
- `dartz` - Functional programming (Either type)
|
|
- `flutter_secure_storage` - Secure token storage
|
|
- `dio` - HTTP client (via ApiClient)
|
|
- `equatable` - Value equality
|
|
- `go_router` - Navigation
|
|
|
|
### Internal Dependencies
|
|
- `core/network/api_client.dart` - HTTP client wrapper
|
|
- `core/storage/secure_storage.dart` - Secure storage wrapper
|
|
- `core/errors/failures.dart` - Error types
|
|
- `core/errors/exceptions.dart` - Exception types
|
|
- `core/widgets/custom_button.dart` - Button widget
|
|
- `core/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 `/warehouses` route
|
|
- 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
|
|
1. **Biometric Authentication**
|
|
- Face ID / Touch ID support
|
|
- Fallback to password
|
|
|
|
2. **Token Auto-Refresh**
|
|
- Background token refresh
|
|
- Seamless reauthentication
|
|
|
|
3. **Multi-factor Authentication**
|
|
- OTP support
|
|
- SMS verification
|
|
|
|
4. **Remember Me**
|
|
- Optional persistent login
|
|
- Secure device storage
|
|
|
|
5. **Password Reset**
|
|
- Email-based reset flow
|
|
- Security questions
|
|
|
|
## Contributing
|
|
|
|
When modifying this feature:
|
|
|
|
1. Follow clean architecture principles
|
|
2. Maintain separation of concerns (data/domain/presentation)
|
|
3. Add tests for new functionality
|
|
4. Update this README with changes
|
|
5. 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`
|