# Authentication Feature Complete JWT-based authentication system for the Retail POS app. ## Overview This feature implements a production-ready authentication system with: - User login with email/password - User registration - JWT token management with secure storage - Automatic token injection in API requests - Profile management - Token refresh capability - Proper error handling ## Architecture The authentication feature follows Clean Architecture principles: ``` auth/ ├── domain/ # Business logic layer │ ├── entities/ # Core business objects │ │ ├── user.dart │ │ └── auth_response.dart │ └── repositories/ # Repository interfaces │ └── auth_repository.dart │ ├── data/ # Data layer │ ├── models/ # Data transfer objects and models │ │ ├── login_dto.dart │ │ ├── register_dto.dart │ │ ├── user_model.dart │ │ └── auth_response_model.dart │ ├── datasources/ # Remote data sources │ │ └── auth_remote_datasource.dart │ └── repositories/ # Repository implementations │ └── auth_repository_impl.dart │ └── presentation/ # UI layer ├── providers/ # Riverpod state management │ └── auth_provider.dart ├── pages/ # UI screens │ ├── login_page.dart │ └── register_page.dart └── widgets/ # Reusable UI components ``` ## API Endpoints Base URL: `http://localhost:3000/api` ### 1. Login ``` POST /auth/login Content-Type: application/json Request: { "email": "user@example.com", "password": "Password123!" } Response (200): { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "id": "uuid", "name": "John Doe", "email": "user@example.com", "roles": ["user"], "isActive": true, "createdAt": "2025-01-01T00:00:00.000Z", "updatedAt": "2025-01-01T00:00:00.000Z" } } ``` ### 2. Register ``` POST /auth/register Content-Type: application/json Request: { "name": "John Doe", "email": "user@example.com", "password": "Password123!", "roles": ["user"] } Response (201): { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { ... } } ``` ### 3. Get Profile ``` GET /auth/profile Authorization: Bearer {token} Response (200): { "id": "uuid", "name": "John Doe", "email": "user@example.com", "roles": ["user"], "isActive": true, "createdAt": "2025-01-01T00:00:00.000Z", "updatedAt": "2025-01-01T00:00:00.000Z" } ``` ### 4. Refresh Token ``` POST /auth/refresh Authorization: Bearer {token} Response (200): { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { ... } } ``` ## Usage ### 1. Setup (Already configured in injection_container.dart) The authentication system is registered in the dependency injection container: ```dart // In lib/core/di/injection_container.dart Future initDependencies() async { // Secure Storage sl.registerLazySingleton(() => SecureStorage()); // Auth Remote Data Source sl.registerLazySingleton( () => AuthRemoteDataSourceImpl(dioClient: sl()), ); // Auth Repository sl.registerLazySingleton( () => AuthRepositoryImpl( remoteDataSource: sl(), secureStorage: sl(), dioClient: sl(), ), ); } ``` ### 2. Login Flow ```dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:retail/features/auth/presentation/providers/auth_provider.dart'; // In a widget class MyWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return ElevatedButton( onPressed: () async { final success = await ref.read(authProvider.notifier).login( email: 'user@example.com', password: 'Password123!', ); if (success) { // Navigate to home } else { // Show error final error = ref.read(authProvider).errorMessage; print('Login failed: $error'); } }, child: Text('Login'), ); } } ``` ### 3. Check Authentication Status ```dart // Check if user is authenticated final isAuthenticated = ref.watch(isAuthenticatedProvider); // Get current user final user = ref.watch(currentUserProvider); if (user != null) { print('Welcome ${user.name}!'); print('Roles: ${user.roles}'); print('Is Admin: ${user.isAdmin}'); } ``` ### 4. Logout ```dart await ref.read(authProvider.notifier).logout(); ``` ### 5. Register New User ```dart final success = await ref.read(authProvider.notifier).register( name: 'John Doe', email: 'john@example.com', password: 'Password123!', roles: ['user'], // Optional, defaults to ['user'] ); ``` ### 6. Refresh Token ```dart final success = await ref.read(authProvider.notifier).refreshToken(); if (!success) { // Token refresh failed, user logged out automatically } ``` ## Bearer Token Injection The authentication system automatically injects the JWT bearer token into all API requests: ### How it Works 1. **On Login/Register**: Token is saved to secure storage and set in DioClient 2. **Automatic Injection**: DioClient interceptor adds `Authorization: Bearer {token}` header to all requests 3. **On Logout**: Token is cleared from secure storage and DioClient 4. **On App Start**: Token is loaded from secure storage and set in DioClient if valid ### Implementation Details ```dart // In lib/core/network/dio_client.dart class DioClient { String? _authToken; DioClient() { _dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) { if (_authToken != null) { options.headers['Authorization'] = 'Bearer $_authToken'; } return handler.next(options); }, ), ); } void setAuthToken(String token) { _authToken = token; } void clearAuthToken() { _authToken = null; } } ``` ### Manual Token Management (Advanced) If you need to manually manage tokens: ```dart // Get token from storage final token = await sl().getAccessToken(); // Set token in DioClient sl().setAuthToken(token!); // Clear token sl().clearAuthToken(); await sl().deleteAllTokens(); ``` ## Error Handling The authentication system handles various error scenarios: ### Error Types 1. **InvalidCredentialsFailure**: Wrong email or password 2. **ConflictFailure**: Email already exists (registration) 3. **UnauthorizedFailure**: Invalid or expired token 4. **NetworkFailure**: No internet connection 5. **ServerFailure**: Server errors (500, 503, etc.) 6. **ValidationFailure**: Invalid input data ### Error Handling Example ```dart final success = await ref.read(authProvider.notifier).login( email: email, password: password, ); if (!success) { final error = ref.read(authProvider).errorMessage; // Show user-friendly error message ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(error ?? 'Login failed'), backgroundColor: Colors.red, ), ); } ``` ## Secure Storage JWT tokens are stored securely using `flutter_secure_storage`: - **iOS**: Keychain - **Android**: EncryptedSharedPreferences - **Web**: Web Crypto API - **Desktop**: Platform-specific secure storage ```dart // Access secure storage final secureStorage = sl(); // Save token await secureStorage.saveAccessToken(token); // Get token final token = await secureStorage.getAccessToken(); // Check if token exists final hasToken = await secureStorage.hasAccessToken(); // Delete token await secureStorage.deleteAllTokens(); ``` ## State Management The authentication state is managed using Riverpod 3.0: ### AuthState ```dart class AuthState { final User? user; // Current authenticated user final bool isAuthenticated; // Authentication status final bool isLoading; // Loading state final String? errorMessage; // Error message (if any) } ``` ### Watching Auth State ```dart // Watch entire auth state final authState = ref.watch(authProvider); // Access specific properties final user = authState.user; final isLoading = authState.isLoading; final error = authState.errorMessage; // Or use convenience providers final isAuthenticated = ref.watch(isAuthenticatedProvider); final currentUser = ref.watch(currentUserProvider); ``` ## Protected Routes Implement route guards to protect authenticated routes: ```dart class AuthGuard extends ConsumerWidget { final Widget child; const AuthGuard({required this.child}); @override Widget build(BuildContext context, WidgetRef ref) { final isAuthenticated = ref.watch(isAuthenticatedProvider); final isLoading = ref.watch(authProvider.select((s) => s.isLoading)); if (isLoading) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } if (!isAuthenticated) { return const LoginPage(); } return child; } } // Usage MaterialApp( home: AuthGuard( child: HomePage(), ), ); ``` ## Testing ### Unit Tests ```dart test('login should return success with valid credentials', () async { // Arrange final repository = MockAuthRepository(); when(repository.login(email: any, password: any)) .thenAnswer((_) async => Right(mockAuthResponse)); // Act final result = await repository.login( email: 'test@example.com', password: 'Password123!', ); // Assert expect(result.isRight(), true); }); ``` ### Integration Tests Test the complete authentication flow from UI to API. ## Code Generation Run code generation for Riverpod providers: ```bash # Generate auth_provider.g.dart flutter pub run build_runner build --delete-conflicting-outputs # Watch for changes flutter pub run build_runner watch ``` ## Production Considerations 1. **HTTPS**: Always use HTTPS in production 2. **Token Expiry**: Implement automatic token refresh on 401 errors 3. **Biometric Auth**: Add fingerprint/face ID support 4. **Password Strength**: Enforce strong password requirements 5. **Rate Limiting**: Handle rate limiting (429 errors) 6. **Secure Storage**: Tokens are already stored securely 7. **Session Management**: Clear tokens on app uninstall ## Future Enhancements - [ ] Biometric authentication (fingerprint, face ID) - [ ] Remember me functionality - [ ] Password reset flow - [ ] Email verification - [ ] Social login (Google, Apple, Facebook) - [ ] Multi-factor authentication (MFA) - [ ] Session timeout warning - [ ] Device management