fill
This commit is contained in:
252
lib/features/auth/QUICK_REFERENCE.md
Normal file
252
lib/features/auth/QUICK_REFERENCE.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# Authentication Feature - Quick Reference
|
||||
|
||||
## Import
|
||||
|
||||
```dart
|
||||
import 'package:minhthu/features/auth/auth.dart';
|
||||
```
|
||||
|
||||
## Common Usage Patterns
|
||||
|
||||
### 1. Login
|
||||
```dart
|
||||
ref.read(authProvider.notifier).login(username, password);
|
||||
```
|
||||
|
||||
### 2. Logout
|
||||
```dart
|
||||
ref.read(authProvider.notifier).logout();
|
||||
```
|
||||
|
||||
### 3. Check if Authenticated
|
||||
```dart
|
||||
final isAuthenticated = ref.watch(isAuthenticatedProvider);
|
||||
```
|
||||
|
||||
### 4. Get Current User
|
||||
```dart
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user != null) {
|
||||
print('Logged in as: ${user.username}');
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Watch Auth State
|
||||
```dart
|
||||
final authState = ref.watch(authProvider);
|
||||
|
||||
if (authState.isLoading) {
|
||||
return LoadingIndicator();
|
||||
}
|
||||
|
||||
if (authState.error != null) {
|
||||
return ErrorView(message: authState.error!);
|
||||
}
|
||||
|
||||
if (authState.isAuthenticated) {
|
||||
return HomeView(user: authState.user!);
|
||||
}
|
||||
|
||||
return LoginView();
|
||||
```
|
||||
|
||||
### 6. Listen to Auth Changes
|
||||
```dart
|
||||
ref.listen(authProvider, (previous, next) {
|
||||
if (next.isAuthenticated) {
|
||||
context.go('/home');
|
||||
} else if (next.error != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(next.error!)),
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Key Classes
|
||||
|
||||
### AuthState
|
||||
```dart
|
||||
class AuthState {
|
||||
final UserEntity? user;
|
||||
final bool isAuthenticated;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
}
|
||||
```
|
||||
|
||||
### UserEntity
|
||||
```dart
|
||||
class UserEntity {
|
||||
final String userId;
|
||||
final String username;
|
||||
final String accessToken;
|
||||
final String? refreshToken;
|
||||
}
|
||||
```
|
||||
|
||||
### LoginRequestModel
|
||||
```dart
|
||||
final request = LoginRequestModel(
|
||||
username: 'john.doe',
|
||||
password: 'secure123',
|
||||
);
|
||||
```
|
||||
|
||||
## Providers
|
||||
|
||||
| Provider | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `authProvider` | `StateNotifier<AuthState>` | Main auth state |
|
||||
| `isAuthenticatedProvider` | `bool` | Check auth status |
|
||||
| `currentUserProvider` | `UserEntity?` | Get current user |
|
||||
| `isAuthLoadingProvider` | `bool` | Check loading state |
|
||||
| `authErrorProvider` | `String?` | Get error message |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/v1/auth/login` | POST | Login |
|
||||
| `/api/v1/auth/logout` | POST | Logout |
|
||||
| `/api/v1/auth/refresh` | POST | Refresh token |
|
||||
|
||||
## Error Types
|
||||
|
||||
- `ValidationFailure` - Invalid input
|
||||
- `AuthenticationFailure` - Login failed
|
||||
- `NetworkFailure` - Network error
|
||||
- `ServerFailure` - Server error
|
||||
|
||||
## Protected Route Example
|
||||
|
||||
```dart
|
||||
class MyPage extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final authState = ref.watch(authProvider);
|
||||
|
||||
if (!authState.isAuthenticated) {
|
||||
return LoginPage();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Protected Page'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.logout),
|
||||
onPressed: () => ref.read(authProvider.notifier).logout(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: Text('Hello, ${authState.user!.username}!'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Check Auth on App Start
|
||||
```dart
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.microtask(() {
|
||||
ref.read(authProvider.notifier).checkAuthStatus();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Show Loading Overlay
|
||||
```dart
|
||||
if (ref.watch(isAuthLoadingProvider)) {
|
||||
return Stack(
|
||||
children: [
|
||||
yourContent,
|
||||
LoadingIndicator.overlay(),
|
||||
],
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Navigation
|
||||
```dart
|
||||
ref.listen(authProvider, (previous, next) {
|
||||
if (!previous!.isAuthenticated && next.isAuthenticated) {
|
||||
context.go('/home');
|
||||
} else if (previous.isAuthenticated && !next.isAuthenticated) {
|
||||
context.go('/login');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Helpers
|
||||
|
||||
```dart
|
||||
// Mock auth state
|
||||
final mockAuthState = AuthState.authenticated(
|
||||
UserEntity(
|
||||
userId: '123',
|
||||
username: 'test',
|
||||
accessToken: 'token',
|
||||
),
|
||||
);
|
||||
|
||||
// Create test container
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
authProvider.overrideWith((ref) => MockAuthNotifier()),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
## Files Reference
|
||||
|
||||
| Layer | File | Purpose |
|
||||
|-------|------|---------|
|
||||
| **Data** | `login_request_model.dart` | Request DTO |
|
||||
| | `user_model.dart` | User DTO |
|
||||
| | `auth_remote_datasource.dart` | API calls |
|
||||
| | `auth_repository_impl.dart` | Repository impl |
|
||||
| **Domain** | `user_entity.dart` | Domain entity |
|
||||
| | `auth_repository.dart` | Repository interface |
|
||||
| | `login_usecase.dart` | Business logic |
|
||||
| **Presentation** | `login_page.dart` | Login UI |
|
||||
| | `login_form.dart` | Form widget |
|
||||
| | `auth_provider.dart` | State management |
|
||||
| **DI** | `auth_dependency_injection.dart` | Providers setup |
|
||||
|
||||
## Troubleshooting Quick Fixes
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Provider not found | Add `ProviderScope` to main.dart |
|
||||
| Navigation fails | Check router configuration |
|
||||
| Tokens not saved | Verify secure storage setup |
|
||||
| API calls fail | Check base URL in constants |
|
||||
| State not updating | Use `ConsumerWidget` |
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. Use `ref.read()` for one-time operations
|
||||
2. Use `ref.watch()` for reactive updates
|
||||
3. Use `ref.listen()` for side effects
|
||||
4. Avoid rebuilding entire tree - scope providers
|
||||
5. Use `select()` for partial state watching
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [x] Tokens in secure storage
|
||||
- [x] Password fields obscured
|
||||
- [x] No logging of sensitive data
|
||||
- [x] Token auto-added to headers
|
||||
- [x] Token cleared on logout
|
||||
- [x] Input validation
|
||||
- [ ] HTTPS only (configure in production)
|
||||
- [ ] Token expiration handling
|
||||
- [ ] Rate limiting
|
||||
- [ ] Biometric auth (optional)
|
||||
Reference in New Issue
Block a user