docs: add Flutter auth integration design

This commit is contained in:
qzl
2026-02-25 14:00:22 +08:00
parent 372be9e676
commit 56e5c66291
@@ -0,0 +1,235 @@
# 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