Spaces:
Running
Running
A newer version of the Gradio SDK is available:
6.6.0
π 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:
{
"success": true,
"data": {
"user": {...},
"token": "eyJhbG..."
}
}
After:
{
"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:
{
"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:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
Success Response (200):
{
"success": true,
"message": "Tokens refreshed successfully.",
"data": {
"access_token": "eyJhbG...",
"refresh_token": "eyJhbG...",
"token_type": "bearer",
"expires_in": 900
}
}
Error Response (401):
{
"detail": "Invalid or expired refresh token. Please login again."
}
π± Flutter Implementation Guide
1. Update Token Storage
// 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
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
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
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
// 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:
{
"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