# Flutter Auth Integration Design

Date: 2026-02-25
Status: Approved

## Summary

Integrate Flutter mobile app with backend auth APIs, including signup, login, token management, and route protection.

## Scope

- Signup flow: `/auth/signup/start` + `/auth/signup/verify`
- Login flow: `/auth/login`
- Token management: access token, refresh token, secure storage
- Route protection: redirect unauthenticated users

Out of scope:
- OTP login (removed from UI)
- Password reset
- Social login

## Architecture

### Directory Structure

```
lib/
├── core/
│   ├── api/
│   │   ├── api_client.dart           # dio wrapper
│   │   ├── api_interceptor.dart      # token injection, refresh logic
│   │   └── api_exception.dart        # unified error types
│   ├── storage/
│   │   └── token_storage.dart        # flutter_secure_storage wrapper
│   └── di/
│       └── injection.dart            # get_it configuration
│
├── features/auth/
│   ├── data/
│   │   ├── models/
│   │   │   ├── signup_request.dart
│   │   │   ├── login_request.dart
│   │   │   └── auth_response.dart
│   │   ├── auth_api.dart             # API interface
│   │   ├── auth_repository.dart      # abstract interface
│   │   └── auth_repository_impl.dart # implementation
│   ├── presentation/
│   │   ├── bloc/
│   │   │   ├── auth_bloc.dart
│   │   │   ├── auth_event.dart
│   │   │   └── auth_state.dart
│   │   └── cubits/
│   │       ├── register_cubit.dart   # signup form state
│   │       └── login_cubit.dart      # login form state
│   └── ui/screens/                   # existing UI adjustments
│
└── main.dart                         # DI initialization
```

### API Layer

**ApiInterceptor responsibilities**:
1. Auto-inject `Authorization: Bearer <access_token>`
2. On 401 response, auto-refresh token and retry
3. If refresh fails, trigger logout

**AuthApi interface**:
```dart
abstract class AuthApi {
  Future<AuthResponse> signupStart(SignupStartRequest request);
  Future<AuthResponse> signupVerify(SignupVerifyRequest request);
  Future<AuthResponse> signupResend(String email);
  Future<AuthResponse> login(LoginRequest request);
  Future<AuthResponse> refresh(String refreshToken);
  Future<void> logout(String refreshToken);
}
```

**TokenStorage interface**:
```dart
abstract class TokenStorage {
  Future<String?> getAccessToken();
  Future<String?> getRefreshToken();
  Future<void> saveTokens({required String access, required String refresh});
  Future<void> clear();
}
```

**Error handling**:
- `ApiException` wraps network errors, timeouts, 4xx/5xx
- UI layer displays user-friendly messages via `ApiException`

### State Management

**AuthBloc (global auth state)**:
```dart
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthAuthenticated extends AuthState {
  final User user;
  final String accessToken;
}
class AuthUnauthenticated extends AuthState {}
class AuthLoading extends AuthState {}

// Events
class AuthStarted extends AuthEvent {}        // Check token on app start
class AuthLoggedIn extends AuthEvent {}       // Login success
class AuthLoggedOut extends AuthEvent {}      // Logout
class AuthTokenRefreshed extends AuthEvent {} // Token refreshed
```

**RegisterCubit (signup form)**:
```dart
class RegisterState {
  final Username username;    // formz input
  final Email email;
  final Password password;
  final FormzSubmissionStatus status;
  final String? errorMessage;
  final String? pendingEmail; // stored after signup/start success
}
```

**LoginCubit (login form)**:
```dart
class LoginState {
  final Email email;
  final Password password;
  final FormzSubmissionStatus status;
  final String? errorMessage;
}
```

### UI Adjustments

**Signup flow changes**:

| Original | New |
|----------|-----|
| Step1: nickname + email | Step1: username + email + password -> call `/signup/start` |
| Step2: password + code + invite | Step2: code + invite(kept) -> call `/signup/verify` |

**Login flow changes**:
- Remove "Login with OTP" button
- `login_email_screen.dart` -> `login_password_screen.dart` passes email
- Password page calls `/login`, navigates to `/home` on success

**Route protection**:
- Use `GoRouter` `redirect` logic
- Unauthenticated user accessing protected route -> redirect to `/`
- Authenticated user accessing `/login` or `/register` -> redirect to `/home`

```dart
redirect: (context, state) {
  final isAuthenticated = // check AuthBloc state;
  final isAuthRoute = state.matchedLocation.startsWith('/login') 
                   || state.matchedLocation.startsWith('/register');
  
  if (!isAuthenticated && !isAuthRoute) return '/';
  if (isAuthenticated && isAuthRoute) return '/home';
  return null;
}
```

### Dependency Injection

**DI configuration**:
```dart
final sl = GetIt.instance;

Future<void> configureDependencies() async {
  // Core
  sl.registerSingleton<Dio>(Dio(BaseOptions(baseUrl: env.apiUrl)));
  sl.registerSingleton<TokenStorage>(SecureTokenStorage());
  sl.registerSingleton<ApiClient>(ApiClient(sl(), sl()));
  
  // Auth
  sl.registerSingleton<AuthApi>(AuthApiImpl(sl()));
  sl.registerSingleton<AuthRepository>(AuthRepositoryImpl(sl(), sl()));
  sl.registerSingleton<AuthBloc>(AuthBloc(sl()));
}
```

**App initialization flow**:
```dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await configureDependencies();
  
  final authBloc = sl<AuthBloc>();
  authBloc.add(AuthStarted()); // Check local token
  
  runApp(MyApp(authBloc: authBloc));
}
```

**Complete flow**:
```
App Start
    |
AuthStarted event
    |
Check TokenStorage
    |
Has token? --No--> AuthUnauthenticated -> Show login
    |
   Yes
    |
Validate token (call /auth/refresh)
    |
Success? --No--> Clear token -> AuthUnauthenticated
    |
   Yes
    |
AuthAuthenticated -> Navigate to /home
```

## Decisions

1. **Adjust Flutter UI to match backend API** - Move password to Step1, call `/signup/start` with all data
2. **Keep invite code UI but don't send** - Backend doesn't support yet, preserve UI for future
3. **Remove OTP login entry** - Backend doesn't have OTP login API
4. **Use complete Bloc architecture** - AuthBloc + Cubits for forms, better testability and extensibility

## Risks

- Token refresh race conditions: ApiInterceptor should handle concurrent requests during refresh
- Secure storage availability: fallback to SharedPreferences on platforms without secure storage

## Testing Strategy

- Unit tests for: Cubits, AuthBloc, Repository, API client
- Widget tests for: form validation, error display
- Integration tests for: complete signup/login flows
