AIDA / docs /AUTH_MIGRATION_GUIDE.md
destinyebuka's picture
fyp
adbcca0
# πŸ”„ Authentication API Changes - Frontend Migration Guide
**Date:** December 24, 2024
**Breaking Changes:** Yes (token format changed)
---
## Summary of Changes
The authentication system has been upgraded to a **WhatsApp-style dual-token strategy** for improved security. This affects login, signup, and token handling.
---
## 🚨 Breaking Changes
### 1. Login/Signup Response Format Changed
**Before:**
```json
{
"success": true,
"data": {
"user": {...},
"token": "eyJhbG..."
}
}
```
**After:**
```json
{
"success": true,
"data": {
"user": {...},
"access_token": "eyJhbG...",
"refresh_token": "eyJhbG...",
"token_type": "bearer",
"expires_in": 900
}
}
```
| Field | Description |
|-------|-------------|
| `access_token` | Use for API calls (15 min expiry) |
| `refresh_token` | Use to get new access tokens (30 day expiry) |
| `expires_in` | Seconds until access_token expires |
---
### 2. Token Expiry Times
| Before | After |
|--------|-------|
| Single token: 60 days | Access token: 15 minutes |
| | Refresh token: 30 days |
---
### 3. OTP Length Changed
| Before | After |
|--------|-------|
| 4 digits (1234) | 6 digits (123456) |
Update your OTP input field to accept 6 characters.
---
### 4. Password Requirements Added
Passwords must now contain:
- βœ… At least 8 characters
- βœ… At least 1 uppercase letter (A-Z)
- βœ… At least 1 lowercase letter (a-z)
- βœ… At least 1 number (0-9)
**Error Response:**
```json
{
"success": false,
"message": "Password must contain at least 8 characters, an uppercase letter, a lowercase letter, a number"
}
```
---
## πŸ†• New Endpoint: Token Refresh
### `POST /api/auth/refresh`
Use this to get new access tokens without re-login.
**Request:**
```json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
```
**Success Response (200):**
```json
{
"success": true,
"message": "Tokens refreshed successfully.",
"data": {
"access_token": "eyJhbG...",
"refresh_token": "eyJhbG...",
"token_type": "bearer",
"expires_in": 900
}
}
```
**Error Response (401):**
```json
{
"detail": "Invalid or expired refresh token. Please login again."
}
```
---
## πŸ“± Flutter Implementation Guide
### 1. Update Token Storage
```dart
// OLD: Single token
await secureStorage.write(key: 'token', value: token);
// NEW: Store both tokens
await secureStorage.write(key: 'access_token', value: data['access_token']);
await secureStorage.write(key: 'refresh_token', value: data['refresh_token']);
```
### 2. Add Token Refresh Interceptor
```dart
class AuthInterceptor extends Interceptor {
final Dio dio;
final SecureStorage storage;
AuthInterceptor(this.dio, this.storage);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await storage.read(key: 'access_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException error, ErrorInterceptorHandler handler) async {
if (error.response?.statusCode == 401) {
// Access token expired - try to refresh
final refreshed = await _refreshTokens();
if (refreshed) {
// Retry the original request with new token
final newToken = await storage.read(key: 'access_token');
error.requestOptions.headers['Authorization'] = 'Bearer $newToken';
final response = await dio.fetch(error.requestOptions);
return handler.resolve(response);
} else {
// Refresh failed - logout user
await _logout();
}
}
handler.next(error);
}
Future<bool> _refreshTokens() async {
try {
final refreshToken = await storage.read(key: 'refresh_token');
if (refreshToken == null) return false;
final response = await Dio().post(
'$baseUrl/api/auth/refresh',
data: {'refresh_token': refreshToken},
);
if (response.data['success']) {
await storage.write(key: 'access_token', value: response.data['data']['access_token']);
await storage.write(key: 'refresh_token', value: response.data['data']['refresh_token']);
return true;
}
return false;
} catch (e) {
return false;
}
}
Future<void> _logout() async {
await storage.delete(key: 'access_token');
await storage.delete(key: 'refresh_token');
// Navigate to login screen
}
}
```
### 3. Update Login Handler
```dart
Future<void> handleLogin(String identifier, String password) async {
final response = await dio.post('/api/auth/login', data: {
'identifier': identifier,
'password': password,
});
if (response.data['success']) {
final data = response.data['data'];
// Store BOTH tokens
await storage.write(key: 'access_token', value: data['access_token']);
await storage.write(key: 'refresh_token', value: data['refresh_token']);
// Store user data
final user = User.fromJson(data['user']);
await saveUser(user);
// Navigate to home
navigateToHome();
}
}
```
### 4. Update Password Validation UI
```dart
String? validatePassword(String password) {
if (password.length < 8) {
return 'Password must be at least 8 characters';
}
if (!password.contains(RegExp(r'[A-Z]'))) {
return 'Password must contain an uppercase letter';
}
if (!password.contains(RegExp(r'[a-z]'))) {
return 'Password must contain a lowercase letter';
}
if (!password.contains(RegExp(r'[0-9]'))) {
return 'Password must contain a number';
}
return null;
}
```
### 5. Update OTP Input
```dart
// Change maxLength from 4 to 6
TextField(
controller: otpController,
maxLength: 6, // Changed from 4
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: '123456', // Updated hint
),
)
```
---
## πŸ”„ Token Flow Diagram
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ USER LOGIN β”‚
β”‚ POST /api/auth/login β”‚
β”‚ Returns: access_token (15m) + refresh_token (30d) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ NORMAL API CALLS β”‚
β”‚ Header: Authorization: Bearer <access_token> β”‚
β”‚ β†’ API responds normally β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό (after 15 minutes)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ACCESS TOKEN EXPIRES β”‚
β”‚ API returns 401 Unauthorized β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ AUTOMATIC REFRESH β”‚
β”‚ POST /api/auth/refresh β”‚
β”‚ Body: { "refresh_token": "..." } β”‚
β”‚ Returns: NEW access_token + NEW refresh_token β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ RETRY ORIGINAL REQUEST β”‚
β”‚ With new access_token β†’ Success! β”‚
β”‚ User never notices! ✨ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
---
## ⚑ Rate Limiting (New)
Auth endpoints now have rate limits to prevent brute force:
| Endpoint | Limit |
|----------|-------|
| `/login` | 100 requests/minute per IP |
| `/signup` | 100 requests/minute per IP |
| `/verify-signup-otp` | 100 requests/minute per IP |
**429 Response:**
```json
{
"detail": "Too many login attempts. Please try again later."
}
```
Handle this by showing a "Please wait" message.
---
## βœ… Migration Checklist
- [ ] Update login response parsing for `access_token` + `refresh_token`
- [ ] Update signup response parsing
- [ ] Add token refresh interceptor
- [ ] Store both tokens securely
- [ ] Update OTP input to 6 digits
- [ ] Add password strength validation UI
- [ ] Handle 429 rate limit errors
- [ ] Test token refresh flow